diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0f94601 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +build +config.yaml \ No newline at end of file diff --git a/.gitea_old/workflows/build.yaml b/.gitea_old/workflows/build.yaml new file mode 100644 index 0000000..060d2ef --- /dev/null +++ b/.gitea_old/workflows/build.yaml @@ -0,0 +1,97 @@ +name: Build + +on: + push: + branches: + - main +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + - id: commit + uses: prompt/actions-commit-hash@v3 + - name: Setup go + uses: actions/setup-go@v4 + with: + go-version: '^1.22' + - name: Get dependencies + run: | + go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.cn,direct + go mod download + - name: Build + run: | + CGO_ENABLED=0 go build -ldflags "-w -s" -gcflags "-N -l" -o main . + - name: 'Login to Container Registry' + uses: docker/login-action@v3 + with: + registry: leafdev.top + username: ${{ gitea.actor }} + password: ${{ secrets.REGISTRY_TOKEN }} + - name: 'Build Inventory Image' + run: | + docker build . -f Dockerfile2 --tag leafdev.top/leaf/rag-new:${{ steps.commit.outputs.short }} + docker push leafdev.top/leaf/rag-new:${{ steps.commit.outputs.short }} + docker tag leafdev.top/leaf/rag-new:${{ steps.commit.outputs.short }} leafdev.top/leaf/rag-new:latest + - name: 'Checkout Manifests branch' + uses: actions/checkout@v4 + with: + ref: manifests + + - name: 'Patch API Manifest' + uses: fjogeleit/yaml-update-action@main + with: + valueFile: 'manifests/deployment-api.yaml' + propertyPath: 'spec.template.spec.containers[0].image' + value: 'leafdev.top/leaf/rag-new:${{ steps.commit.outputs.short }}' + commitChange: false + - name: 'Patch Schedule Manifest' + uses: fjogeleit/yaml-update-action@main + with: + valueFile: 'manifests/deployment-schedule.yaml' + propertyPath: 'spec.template.spec.containers[0].image' + value: 'leafdev.top/leaf/rag-new:${{ steps.commit.outputs.short }}' + commitChange: false + - name: Push + run: | + git config user.name ${{ gitea.actor }} + git config user.email ${{ gitea.actor }}@users.noreply.leafdev.top + git add manifests/deployment-api.yaml + git add manifests/deployment-schedule.yaml + git commit -m "Update manifests" + git push origin manifests +# - name: docker +# run: | +# - name: Build Docker Image +# run: | +# docker build -t ${{ env.REGISTRY }}/${{ steps.meta.outputs.tags }} -f ./docker/nginx/Dockerfile . +# - name: Push Docker Image +# run: | +# docker login --username=${{ secrets.DOCKER_USERNAME }} --password ${{ secrets.DOCKER_PASSWORD }} ${{ env.REGISTRY }} +# docker push ${{ env.REGISTRY }}/${{ steps.meta.outputs.tags }} +# - name: Artifact +# uses: christopherhx/gitea-upload-artifact@v4 +# with: +# name: artifact +# path: cmd/main +# push: +# runs-on: ubuntu-latest +## needs: [build] +# steps: +# - uses: https://github.com/actions/checkout@v4 +# - name: Set up Docker Buildx +# uses: https://github.com/docker/setup-buildx-action@v3 +# with: +# config-inline: | +# [registry.":5000"] +# http = true +# insecure = true +# - name: Build and push Docker image +# uses: https://github.com/docker/build-push-action@v5 +# with: +# context: . +# file: ./Dockerfile +# push: true +# tags: ":5000/:${{gitea.sha}},:5000/:latest" \ No newline at end of file diff --git a/.gitea_old/workflows/deploy.yaml b/.gitea_old/workflows/deploy.yaml new file mode 100644 index 0000000..cdd4629 --- /dev/null +++ b/.gitea_old/workflows/deploy.yaml @@ -0,0 +1,25 @@ +name: Deploy + +on: [release] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set output + id: vars + run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + - name: Check output + env: + GITHUB_RELEASE_VERSION: ${{ steps.vars.outputs.tag }} + run: | + # check if GITHUB_RELEASE_VERSION is not empty + - name: Deploy + uses: docker/build-push-action@v3 + with: + context: . + file: ./Dockerfile + push: true + tags: leafdev.top/rag-new:latest \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3764eb4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/build +/config.yaml +.idea +.vscode \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e638415 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# docker build -t registry.leafdev.top/leaf/rag-new:v0.0.1-fix . +FROM golang:latest as builder + +COPY . /app + +RUN go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.cn,direct && go mod download +RUN CGO_ENABLED=0 go build -ldflags "-w -s" -gcflags "-N -l" -o main . + +# RUN +FROM alpine:latest + +WORKDIR /app + +COPY --from=builder /app/main /app/main +ENTRYPOINT ["/app/main"] \ No newline at end of file diff --git a/Dockerfile2 b/Dockerfile2 new file mode 100644 index 0000000..db40d19 --- /dev/null +++ b/Dockerfile2 @@ -0,0 +1,8 @@ +# docker build -t registry.leafdev.top/leaf/rag-new:v0.0.1-fix . -f Dockerfile2 +# RUN +FROM alpine:latest + +WORKDIR /app + +COPY ./main /app/main +ENTRYPOINT ["/app/main"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b0a47d0 --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +.PHONY: install-deps generate swag gorm proto buf + +install-deps: + go install github.com/google/wire/cmd/wire@latest + go install github.com/swaggo/swag/cmd/swag@latest + go install github.com/pressly/goose/v3/cmd/goose@latest + echo "You need install buf manually, https://github.com/bufbuild/buf" + +generate: + go generate ./... + +swag: + swag init -g main.go --parseDependency + +gorm: + cd hack/gorm-gen && go run . + +buf: + buf dep update + +proto: + buf generate \ No newline at end of file diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..8d1edf2 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,21 @@ +version: v2 + +plugins: + - remote: buf.build/grpc/go:v1.5.1 + out: proto/gen + opt: + - paths=source_relative + - remote: buf.build/protocolbuffers/go:v1.34.2 + out: proto/gen + opt: + - paths=source_relative + - remote: buf.build/grpc-ecosystem/gateway:v2.23.0 + out: proto/gen + opt: + - paths=source_relative + - generate_unbound_methods=true + - remote: buf.build/grpc-ecosystem/openapiv2:v2.23.0 + out: proto/gen + opt: + - allow_merge +# - merge_file_name=mce-spider \ No newline at end of file diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..a509e84 --- /dev/null +++ b/buf.yaml @@ -0,0 +1,4 @@ +version: v2 + +deps: + - buf.build/googleapis/googleapis diff --git a/cmd/grpc.go b/cmd/grpc.go new file mode 100644 index 0000000..9391ded --- /dev/null +++ b/cmd/grpc.go @@ -0,0 +1,96 @@ +package cmd + +import ( + "context" + "net" + "net/http" + "sync" + + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/spf13/cobra" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/reflection" + v1 "leafdev.top/Leaf/leaf-library-3/proto/gen/proto/api/v1" +) + +func init() { + RootCmd.AddCommand(documentServiceCommand) +} + +var documentServiceCommand = &cobra.Command{ + Use: "grpc", + Short: "Start gRPC", + Run: func(cmd *cobra.Command, args []string) { + app, err := CreateApp() + if err != nil { + panic(err) + return + } + + lis, err := net.Listen("tcp", app.Config.Grpc.Address) + if err != nil { + app.Logger.Sugar.Fatal(err) + } + var opts = []grpc.ServerOption{ + grpc.ChainUnaryInterceptor( + logging.UnaryServerInterceptor(app.Api.GRPC.Interceptor.Logger.ZapLogInterceptor()), + + app.Api.GRPC.Interceptor.Auth.UnaryJWTAuth(), + ), + grpc.ChainStreamInterceptor( + logging.StreamServerInterceptor(app.Api.GRPC.Interceptor.Logger.ZapLogInterceptor()), + app.Api.GRPC.Interceptor.Auth.StreamJWTAuth(), + ), + } + grpcServer := grpc.NewServer(opts...) + + // 注册服务 + v1.RegisterDocumentServiceServer(grpcServer, app.Api.GRPC.DocumentApi) + + // 反射 + reflection.Register(grpcServer) + + app.Logger.Sugar.Infof("gRPC listening on %s, http gateway listening on %s", + app.Config.Grpc.Address, app.Config.Grpc.AddressGateway) + + var wg sync.WaitGroup + + wg.Add(1) + go func() { + if err := grpcServer.Serve(lis); err != nil { + app.Logger.Sugar.Fatal(err) + } + }() + + wg.Add(1) + go func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var dialOption = []grpc.DialOption{ + grpc.WithTransportCredentials(insecure.NewCredentials()), + } + + mux := runtime.NewServeMux() + + var err error + + // 注册服务到网关并转为 Http 请求 + err = v1.RegisterDocumentServiceHandlerFromEndpoint(ctx, mux, app.Config.Grpc.Address, dialOption) + + if err != nil { + app.Logger.Sugar.Fatal(err) + } + + if err := http.ListenAndServe(app.Config.Grpc.AddressGateway, mux); err != nil { + app.Logger.Sugar.Fatal(err) + } + + }() + + wg.Wait() + + }, +} diff --git a/cmd/http.go b/cmd/http.go new file mode 100644 index 0000000..94ef29d --- /dev/null +++ b/cmd/http.go @@ -0,0 +1,102 @@ +package cmd + +import ( + "context" + "errors" + "github.com/gofiber/fiber/v2/middleware/adaptor" + "github.com/spf13/cobra" + "net/http" + "os" + "os/signal" + "strconv" + "syscall" + "time" +) + +func init() { + RootCmd.AddCommand(httpCmd) +} + +var httpCmd = &cobra.Command{ + Use: "http", + Run: func(cmd *cobra.Command, args []string) { + initHttpServer() + }, +} + +func initHttpServer() { + app, err := CreateApp() + if err != nil { + panic(err) + return + } + + if app.Config.Http.Host == "" { + app.Config.Http.Host = "0.0.0.0" + } + + if app.Config.Http.Port == 0 { + app.Config.Http.Port = 8000 + } + + var bizServer *http.Server + var metricServer *http.Server + + bizServer = &http.Server{ + Addr: ":8080", + } + + bizServer.Addr = app.Config.Http.Host + ":" + strconv.Itoa(app.Config.Http.Port) + bizServer.Handler = adaptor.FiberApp(app.HttpServer.BizRouter()) + + // 启动 http + go func() { + // refresh + app.Service.Jwks.SetupAuthRefresh() + + app.Logger.Sugar.Info("Listening and serving HTTP on ", bizServer.Addr) + err = bizServer.ListenAndServe() + if err != nil && !errors.Is(http.ErrServerClosed, err) { + panic(err) + return + } + }() + + // 启动 metrics + if app.Config.Metrics.Enabled { + metricServer = &http.Server{ + Addr: ":8080", + } + metricServer.Addr = app.Config.Metrics.Host + ":" + strconv.Itoa(app.Config.Metrics.Port) + go func() { + app.Logger.Sugar.Info("Metrics and serving HTTP on ", metricServer.Addr) + + metricServer.Handler = adaptor.FiberApp(app.HttpServer.MetricRouter()) + + err = metricServer.ListenAndServe() + if err != nil && !errors.Is(http.ErrServerClosed, err) { + panic(err) + return + } + }() + } + + quit := make(chan os.Signal) + + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + + <-quit + + app.Logger.Sugar.Info("Shutdown http server") + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + + if err := bizServer.Shutdown(ctx); err != nil { + app.Logger.Sugar.Fatalf("Biz Server Shutdown Error: %s", err) + } + + if err := metricServer.Shutdown(ctx); err != nil { + app.Logger.Sugar.Fatalf("Metric Server Shutdown Error: %s", err) + } +} diff --git a/cmd/migrate.go b/cmd/migrate.go new file mode 100644 index 0000000..4b6668d --- /dev/null +++ b/cmd/migrate.go @@ -0,0 +1,158 @@ +package cmd + +import ( + "context" + "fmt" + "os" + "strings" + "time" + + "leafdev.top/Leaf/leaf-library-3/internal/database/migrations" + + "github.com/pressly/goose/v3" + "github.com/spf13/cobra" +) + +func init() { + RootCmd.AddCommand(migrateCommand, createGoMigrateCommand) +} + +var migrateCommand = &cobra.Command{ + Use: "goose [command]", + Short: "goose ", + Long: "Run goose", + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + RunMigrate(args) + }, +} + +var createGoMigrateCommand = &cobra.Command{ + Use: "create-migrate", + Short: "create go migration", + Long: "create go migration using goose.", + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + + name := args[0] + + createGooseMigration(name) + }, +} + +// RunMigrate 为数据库函数 +func RunMigrate(args []string) { + app, err := CreateApp() + if err != nil { + panic(err) + } + + migrations.Config = app.Config + + goose.SetBaseFS(migrations.MigrationFS) + + err = goose.SetDialect("postgres") + if err != nil { + panic(err) + } + + db, err := app.GORM.DB() + if err != nil { + panic(err) + } + + command := args[0] + + var arguments []string + if len(args) > 3 { + arguments = append(arguments, args[3:]...) + } + + err = goose.RunContext(context.Background(), command, db, ".", arguments...) + + if err != nil { + panic(err) + } + + defer func() { + if err := db.Close(); err != nil { + panic(err) + } + }() +} + +func createGooseMigration(name string) { + // 在 internal/database/migrations 目录下新建一个迁移文件 + // 文件名为 yyyy-mm-dd-hh-mm-ss-.go + month := int(time.Now().Month()) + monthString := fmt.Sprintf("%d", month) + if month < 10 { + // 转 string + monthString = "0" + monthString + } + + day := time.Now().Day() + dayString := fmt.Sprintf("%d", day) + if day < 10 { + dayString = "0" + dayString + } + + hour := time.Now().Hour() + hourString := fmt.Sprintf("%d", hour) + if hour < 10 { + hourString = "0" + hourString + } + + minute := time.Now().Minute() + minuteString := fmt.Sprintf("%d", minute) + if minute < 10 { + minuteString = "0" + minuteString + } + + // 秒 + second := time.Now().Second() + secondString := fmt.Sprintf("%d", second) + if second < 10 { + secondString = "0" + secondString + } + + funcName := fmt.Sprintf("%d%s%s%s%s%s", time.Now().Year(), monthString, dayString, hourString, minuteString, secondString) + fileName := fmt.Sprintf("%s_%s.go", funcName, name) + + // 模板内容 + var template = `package migrations + +import ( + "context" + "database/sql" + "github.com/pressly/goose/v3" +) + +func init() { + goose.AddMigrationContext(Up, Down) +} + +func Up(ctx context.Context, tx *sql.Tx) error { + _, err := tx.ExecContext(ctx, "UPDATE users SET username='admin' WHERE username='root';") + return err +} + +func Down(ctx context.Context, tx *sql.Tx) error { + _, err := tx.ExecContext(ctx, "UPDATE users SET username='root' WHERE username='admin';") + return err +} +` + + template = strings.ReplaceAll(template, "", funcName+name) + err := os.WriteFile("internal/database/migrations/"+fileName, []byte(template), 0644) + if err != nil { + panic(fmt.Sprintf("failed creating migration file: %v", err)) + } + +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..15509f8 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,14 @@ +package cmd + +import "github.com/spf13/cobra" + +var RootCmd = &cobra.Command{ + Use: "app", + Run: func(cmd *cobra.Command, args []string) { + err := cmd.Help() + if err != nil { + panic(err) + return + } + }, +} diff --git a/cmd/schedule.go b/cmd/schedule.go new file mode 100644 index 0000000..084e04a --- /dev/null +++ b/cmd/schedule.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "leafdev.top/Leaf/leaf-library-3/internal/base" + + "github.com/spf13/cobra" +) + +func init() { + RootCmd.AddCommand(scheduleCmd) +} + +var scheduleCmd = &cobra.Command{ + Use: "schedule", + Short: "Schedule commands", + Long: `Schedule commands`, + Run: func(cmd *cobra.Command, args []string) { + app, err := CreateApp() + if err != nil { + panic(err) + } + + runSchedule(app) + }, +} + +func runSchedule(app *base.Application) { + // var wg sync.WaitGroup + + // var ctx = context.Background() + + // wg.Add(1) + // // 启动一个定时器 + // go func() { + + // }() + + // wg.Wait() +} diff --git a/cmd/wire.go b/cmd/wire.go new file mode 100644 index 0000000..9951cb9 --- /dev/null +++ b/cmd/wire.go @@ -0,0 +1,44 @@ +//go:build wireinject +// +build wireinject + +package cmd + +import ( + "leafdev.top/Leaf/leaf-library-3/internal/api" + "leafdev.top/Leaf/leaf-library-3/internal/base" + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" + "leafdev.top/Leaf/leaf-library-3/internal/base/logger" + "leafdev.top/Leaf/leaf-library-3/internal/base/milvus" + "leafdev.top/Leaf/leaf-library-3/internal/base/orm" + "leafdev.top/Leaf/leaf-library-3/internal/base/redis" + "leafdev.top/Leaf/leaf-library-3/internal/base/s3" + "leafdev.top/Leaf/leaf-library-3/internal/base/server" + "leafdev.top/Leaf/leaf-library-3/internal/batch" + "leafdev.top/Leaf/leaf-library-3/internal/dao" + "leafdev.top/Leaf/leaf-library-3/internal/router" + "leafdev.top/Leaf/leaf-library-3/internal/service" + + "github.com/google/wire" +) + +var ProviderSet = wire.NewSet( + conf.NewConfig, + logger.NewZapLogger, + orm.NewGORM, + dao.NewQuery, + redis.NewRedis, + s3.NewS3, + milvus.NewService, + batch.NewBatch, + service.Provide, + api.Provide, + router.Provide, + server.NewHTTPServer, + base.NewApplication, +) + +func CreateApp() (*base.Application, error) { + wire.Build(ProviderSet) + + return nil, nil +} diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go new file mode 100644 index 0000000..fe0510d --- /dev/null +++ b/cmd/wire_gen.go @@ -0,0 +1,67 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run -mod=mod github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package cmd + +import ( + "github.com/google/wire" + "leafdev.top/Leaf/leaf-library-3/internal/api" + "leafdev.top/Leaf/leaf-library-3/internal/api/grpc" + "leafdev.top/Leaf/leaf-library-3/internal/api/grpc/interceptor" + "leafdev.top/Leaf/leaf-library-3/internal/api/grpc/v1/documents" + "leafdev.top/Leaf/leaf-library-3/internal/api/http" + "leafdev.top/Leaf/leaf-library-3/internal/api/http/v1" + "leafdev.top/Leaf/leaf-library-3/internal/base" + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" + "leafdev.top/Leaf/leaf-library-3/internal/base/logger" + "leafdev.top/Leaf/leaf-library-3/internal/base/milvus" + "leafdev.top/Leaf/leaf-library-3/internal/base/orm" + "leafdev.top/Leaf/leaf-library-3/internal/base/redis" + "leafdev.top/Leaf/leaf-library-3/internal/base/s3" + "leafdev.top/Leaf/leaf-library-3/internal/base/server" + "leafdev.top/Leaf/leaf-library-3/internal/batch" + "leafdev.top/Leaf/leaf-library-3/internal/dao" + "leafdev.top/Leaf/leaf-library-3/internal/router" + "leafdev.top/Leaf/leaf-library-3/internal/service" + "leafdev.top/Leaf/leaf-library-3/internal/service/auth" + "leafdev.top/Leaf/leaf-library-3/internal/service/jwks" + "leafdev.top/Leaf/leaf-library-3/internal/service/stream" +) + +// Injectors from wire.go: + +func CreateApp() (*base.Application, error) { + config := conf.NewConfig() + loggerLogger := logger.NewZapLogger(config) + jwksJWKS := jwks.NewJWKS(config, loggerLogger) + authService := auth.NewService(config, jwksJWKS, loggerLogger) + userController := v1.NewUserController(authService) + handlers := http.NewHandler(userController) + middleware := http.NewMiddleware(config, loggerLogger, authService) + routerApi := router.NewApiRoute(handlers, middleware) + swaggerRouter := router.NewSwaggerRoute() + httpServer := server.NewHTTPServer(config, routerApi, swaggerRouter, middleware, loggerLogger) + db := orm.NewGORM(config, loggerLogger) + query := dao.NewQuery(db) + handler := documents.NewHandler(query) + interceptorAuth := interceptor.NewAuth(authService, loggerLogger, config) + interceptorLogger := interceptor.NewLogger(loggerLogger) + grpcInterceptor := grpc.NewInterceptor(interceptorAuth, interceptorLogger) + grpcHandlers := grpc.NewHandler(handler, grpcInterceptor) + apiApi := api.NewApi(grpcHandlers, handlers) + streamService := stream.NewService(config) + serviceService := service.NewService(loggerLogger, jwksJWKS, authService, streamService) + redisRedis := redis.NewRedis(config) + batchBatch := batch.NewBatch(loggerLogger) + s3S3 := s3.NewS3(config) + client := milvus.NewService(config, loggerLogger) + application := base.NewApplication(config, httpServer, apiApi, loggerLogger, serviceService, redisRedis, batchBatch, s3S3, db, query, client) + return application, nil +} + +// wire.go: + +var ProviderSet = wire.NewSet(conf.NewConfig, logger.NewZapLogger, orm.NewGORM, dao.NewQuery, redis.NewRedis, s3.NewS3, milvus.NewService, batch.NewBatch, service.Provide, api.Provide, router.Provide, server.NewHTTPServer, base.NewApplication) diff --git a/configs/config.go b/configs/config.go new file mode 100644 index 0000000..681f6bf --- /dev/null +++ b/configs/config.go @@ -0,0 +1,9 @@ +package configs + +import _ "embed" + +//go:embed config.yaml +var Config []byte + +//go:embed rbac_model.conf +var RBACModel []byte diff --git a/configs/config.yaml b/configs/config.yaml new file mode 100644 index 0000000..b327111 --- /dev/null +++ b/configs/config.yaml @@ -0,0 +1,90 @@ +app: + name: template + allowed_audiences: + - "1" # UserLand Personal Access Client + +http: + port: 8080 + host: 0.0.0.0 + # the production url + url: http://127.0.0.1:8080 + cors: + enabled: true + allow_origins: + - "http://localhost:8080" + allow_methods: + - "GET" + - "HEAD" + - "PUT" + - "PATCH" + - "POST" + - "DELETE" + allow_headers: + - "Origin" + - "Content-Length" + - "Content-Type" + - "X-Requested-With" + - "Authorization" + allow_credentials: true + expose_headers: [ ] + max_age: 720 + +grpc: + address: 0.0.0.0:9090 + address_gateway: 0.0.0.0:9091 + +debug: + enabled: false + +database: + host: 127.0.0.1 + port: 5432 + user: root + password: root + name: db_name + sslmode: disable + timezone: "Asia/Shanghai" + +redis: + host: 127.0.0.1 + port: 6379 + password: "" + db: 0 + +jwks: + url: "https://auth.leaflow.cn/.well-known/jwks" + +metrics: + enabled: true + port: 8081 + host: 0.0.0.0 + +s3: + endpoint: 127.0.0.1:9000 + external_endpoint: 127.0.0.1:9000 + access_key: minio + secret_key: minio123 + bucket: amber + use_ssl: false + region: + + +kafka: + bootstrap_servers: + - 127.0.0.1:9092 + topic: "amber" + group_id: "" + # Plain + username: "" + password: "" + +milvus: + host: 127.0.0.1 + port: 19530 + db_name: library + document_collection: documents + user: + password: + +third_party: + openai_api_key: "" \ No newline at end of file diff --git a/configs/rbac_model.conf b/configs/rbac_model.conf new file mode 100644 index 0000000..10027a5 --- /dev/null +++ b/configs/rbac_model.conf @@ -0,0 +1,19 @@ +# Request definition 即为请求定义,代表了可以传入什么样的参数来确定权限 +[request_definition] +r = sub, obj, act + +# Policy definition 代表了规则的组成 +[policy_definition] +p = sub, obj, act + +# g 是一个 RBAC系统, _, _表示角色继承关系的前项和后项,即前项继承后项角色的权限 +[role_definition] +g = _, _ + +# Policy effect 则表示什么样的规则可以被允许, e = some(where (p.eft == allow)) 这句就表示当前请求中包含的任何一个规则被允许的话,这个权限就会被允许 +[policy_effect] +e = some(where (p.eft == allow)) + +# 是策略匹配程序的定义。表示请求与规则是如何起作用的 +[matchers] +m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..47cd9cf --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,128 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/v1/ping": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "测试接口,将会返回当前用户的信息", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ping" + ], + "summary": "Greet", + "deprecated": true, + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Body" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.CurrentUserResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.Body" + } + } + } + } + } + }, + "definitions": { + "response.Body": { + "type": "object", + "properties": { + "data": {}, + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "success": { + "type": "boolean" + } + } + }, + "schema.CurrentUserResponse": { + "type": "object", + "properties": { + "ip": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userName": { + "type": "string" + }, + "valid": { + "type": "boolean" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "API Docs", + Description: "", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..e178447 --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,101 @@ +{ + "swagger": "2.0", + "info": { + "title": "API Docs", + "contact": {}, + "version": "1.0" + }, + "paths": { + "/api/v1/ping": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "测试接口,将会返回当前用户的信息", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "ping" + ], + "summary": "Greet", + "deprecated": true, + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.Body" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.CurrentUserResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.Body" + } + } + } + } + } + }, + "definitions": { + "response.Body": { + "type": "object", + "properties": { + "data": {}, + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "success": { + "type": "boolean" + } + } + }, + "schema.CurrentUserResponse": { + "type": "object", + "properties": { + "ip": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userName": { + "type": "string" + }, + "valid": { + "type": "boolean" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..42be56c --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,62 @@ +definitions: + response.Body: + properties: + data: {} + error: + type: string + message: + type: string + success: + type: boolean + type: object + schema.CurrentUserResponse: + properties: + ip: + type: string + userEmail: + type: string + userId: + type: string + userName: + type: string + valid: + type: boolean + type: object +info: + contact: {} + title: API Docs + version: "1.0" +paths: + /api/v1/ping: + get: + consumes: + - application/json + deprecated: true + description: 测试接口,将会返回当前用户的信息 + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.Body' + - properties: + data: + $ref: '#/definitions/schema.CurrentUserResponse' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.Body' + security: + - ApiKeyAuth: [] + summary: Greet + tags: + - ping +securityDefinitions: + ApiKeyAuth: + in: header + name: Authorization + type: apiKey +swagger: "2.0" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2cec6d0 --- /dev/null +++ b/go.mod @@ -0,0 +1,133 @@ +module leafdev.top/Leaf/leaf-library-3 + +go 1.23.2 + +require ( + github.com/MicahParks/keyfunc/v3 v3.3.5 + github.com/ansrivas/fiberprometheus/v2 v2.7.0 + github.com/bsm/redislock v0.9.4 + github.com/bytedance/sonic v1.12.5 + github.com/gofiber/contrib/fiberzap/v2 v2.1.4 + github.com/gofiber/fiber/v2 v2.52.5 + github.com/gofiber/swagger v1.1.0 + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/google/wire v0.6.0 + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 + github.com/milvus-io/milvus-sdk-go/v2 v2.4.2 + github.com/minio/minio-go/v7 v7.0.80 + github.com/mitchellh/mapstructure v1.5.0 + github.com/pressly/goose/v3 v3.22.1 + github.com/redis/go-redis/v9 v9.7.0 + github.com/segmentio/kafka-go v0.4.47 + github.com/spf13/cobra v1.8.1 + github.com/spf13/viper v1.19.0 + github.com/swaggo/swag v1.16.4 + go.uber.org/zap v1.27.0 + google.golang.org/grpc v1.68.0 + google.golang.org/protobuf v1.35.2 + gorm.io/driver/postgres v1.5.10 + gorm.io/gen v0.3.26 + gorm.io/gorm v1.25.12 + gorm.io/plugin/dbresolver v1.5.3 + moul.io/zapgorm2 v1.3.0 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/MicahParks/jwkset v0.5.20 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bytedance/sonic/loader v0.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/cockroachdb/errors v1.11.3 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/redact v1.1.5 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/getsentry/sentry-go v0.29.1 // indirect + github.com/go-ini/ini v1.67.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.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.7.1 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-sqlite3 v1.14.17 // indirect + github.com/mfridman/interpolate v0.0.2 // indirect + github.com/milvus-io/milvus-proto/go-api/v2 v2.4.16 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.60.1 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/rs/xid v1.6.0 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/swaggo/files/v2 v2.0.1 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.57.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/time v0.8.0 // indirect + golang.org/x/tools v0.27.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/datatypes v1.2.4 // indirect + gorm.io/driver/mysql v1.5.7 // indirect + gorm.io/driver/sqlserver v1.5.3 // indirect + gorm.io/hints v1.1.2 // indirect + modernc.org/sqlite v1.34.2 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..481ff0f --- /dev/null +++ b/go.sum @@ -0,0 +1,568 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0/go.mod h1:Q28U+75mpCaSCDowNEmhIo/rmgdkqmkmzI7N6TGR4UY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= +github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/MicahParks/jwkset v0.5.20 h1:gTIKx9AofTqQJ0srd8AL7ty9NeadP5WUXSPOZadTpOI= +github.com/MicahParks/jwkset v0.5.20/go.mod h1:q8ptTGn/Z9c4MwbcfeCDssADeVQb3Pk7PnVxrvi+2QY= +github.com/MicahParks/keyfunc/v3 v3.3.5 h1:7ceAJLUAldnoueHDNzF8Bx06oVcQ5CfJnYwNt1U3YYo= +github.com/MicahParks/keyfunc/v3 v3.3.5/go.mod h1:SdCCyMJn/bYqWDvARspC6nCT8Sk74MjuAY22C7dCST8= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/ansrivas/fiberprometheus/v2 v2.7.0 h1:09XiSzG0J7aZp7RviklngdWdDbSybKjhuWAstp003Gg= +github.com/ansrivas/fiberprometheus/v2 v2.7.0/go.mod h1:hSJdO65lfnWW70Qn9uGdXXsUUSkckbhuw5r/KesygpU= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bsm/redislock v0.9.4 h1:X/Wse1DPpiQgHbVYRE9zv6m070UcKoOGekgvpNhiSvw= +github.com/bsm/redislock v0.9.4/go.mod h1:Epf7AJLiSFwLCiZcfi6pWFO/8eAYrYpQXFxEDPoDeAk= +github.com/bytedance/sonic v1.12.5 h1:hoZxY8uW+mT+OpkcUWw4k0fDINtOcVavEsGfzwzFU/w= +github.com/bytedance/sonic v1.12.5/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= +github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +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/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/getsentry/sentry-go v0.29.1 h1:DyZuChN8Hz3ARxGVV8ePaNXh1dQ7d76AiB117xcREwA= +github.com/getsentry/sentry-go v0.29.1/go.mod h1:x3AtIzN01d6SiWkderzaH28Tm0lgkafpJ5Bm3li39O0= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-faker/faker/v4 v4.1.0 h1:ffuWmpDrducIUOO0QSKSF5Q2dxAht+dhsT9FvVHhPEI= +github.com/go-faker/faker/v4 v4.1.0/go.mod h1:uuNc0PSRxF8nMgjGrrrU4Nw5cF30Jc6Kd0/FUTTYbhg= +github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= +github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/gofiber/contrib/fiberzap/v2 v2.1.4 h1:GCtCQnT4Cr9az4qab2Ozmqsomkxm4Ei86MfKk/1p5+0= +github.com/gofiber/contrib/fiberzap/v2 v2.1.4/go.mod h1:PkdXgUzw+oj4m6ksfKJ0Hs3H7iPhwvhfI4b2LSA9hhA= +github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= +github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gofiber/swagger v1.1.0 h1:ff3rg1fB+Rp5JN/N8jfxTiZtMKe/9tB9QDc79fPiJKQ= +github.com/gofiber/swagger v1.1.0/go.mod h1:pRZL0Np35sd+lTODTE5The0G+TMHfNY+oC4hM2/i5m8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +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= +github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= +github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= +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/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= +github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +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-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= +github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU= +github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= +github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= +github.com/milvus-io/milvus-proto/go-api/v2 v2.4.16 h1:XcdubT6Vy0PvNrWDJZ4cy6ytXWRENEYgYBCLkI+YpTE= +github.com/milvus-io/milvus-proto/go-api/v2 v2.4.16/go.mod h1:/6UT4zZl6awVeXLeE7UGDWZvXj3IWkRsh3mqsn0DiAs= +github.com/milvus-io/milvus-sdk-go/v2 v2.4.2 h1:Xqf+S7iicElwYoS2Zly8Nf/zKHuZsNy1xQajfdtygVY= +github.com/milvus-io/milvus-sdk-go/v2 v2.4.2/go.mod h1:ulO1YUXKH0PGg50q27grw048GDY9ayB4FPmh7D+FFTA= +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.80 h1:2mdUHXEykRdY/BigLt3Iuu1otL0JTogT0Nmltg0wujk= +github.com/minio/minio-go/v7 v7.0.80/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0= +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/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= +github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pressly/goose/v3 v3.22.1 h1:2zICEfr1O3yTP9BRZMGPj7qFxQ+ik6yeo+z1LMuioLc= +github.com/pressly/goose/v3 v3.22.1/go.mod h1:xtMpbstWyCpyH+0cxLTMCENWBG+0CSxvTsXhW95d5eo= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= +github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= +github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/segmentio/kafka-go v0.4.47 h1:IqziR4pA3vrZq7YdRxaT3w1/5fvIH5qpCwstUanQQB0= +github.com/segmentio/kafka-go v0.4.47/go.mod h1:HjF6XbOKh0Pjlkr5GVZxt6CsjjwnmhVOfURM5KMd8qg= +github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= +github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/swaggo/files/v2 v2.0.1 h1:XCVJO/i/VosCDsJu1YLpdejGsGnBE9deRMpjN4pJLHk= +github.com/swaggo/files/v2 v2.0.1/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= +github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= +github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU= +github.com/tinylib/msgp v1.1.9/go.mod h1:BCXGB54lDD8qUEPmiG0cQQUANC4IUQyB2ItS2UDlO/k= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.57.0 h1:Xw8SjWGEP/+wAAgyy5XTvgrWlOD1+TxbbvNADYCm1Tg= +github.com/valyala/fasthttp v1.57.0/go.mod h1:h6ZBaPRlzpZ6O3H5t2gEk1Qi33+TmLvfwgLLp0t9CpE= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +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= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +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.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f h1:M65LEviCfuZTfrfzwwEoxVtgvfkFkBUbFnRbxCXuXhU= +google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f/go.mod h1:Yo94eF2nj7igQt+TiJ49KxjIH8ndLYPZMIRSiRcEbg0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f h1:C1QccEa9kUwvMgEUORqQD9S17QesQijxjZ84sO82mfo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/grpc/examples v0.0.0-20220617181431-3e7b97febc7f h1:rqzndB2lIQGivcXdTuY3Y9NBvr70X+y77woofSRluec= +google.golang.org/grpc/examples v0.0.0-20220617181431-3e7b97febc7f/go.mod h1:gxndsbNG1n4TZcHGgsYEfVGnTxqfEdfiDv6/DADXX9o= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/datatypes v1.2.4 h1:uZmGAcK/QZ0uyfCuVg0VQY1ZmV9h1fuG0tMwKByO1z4= +gorm.io/datatypes v1.2.4/go.mod h1:f4BsLcFAX67szSv8svwLRjklArSHAvHLeE3pXAS5DZI= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.5.10 h1:7Lggqempgy496c0WfHXsYWxk3Th+ZcW66/21QhVFdeE= +gorm.io/driver/postgres v1.5.10/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c= +gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I= +gorm.io/driver/sqlserver v1.5.3 h1:rjupPS4PVw+rjJkfvr8jn2lJ8BMhT4UW5FwuJY0P3Z0= +gorm.io/driver/sqlserver v1.5.3/go.mod h1:B+CZ0/7oFJ6tAlefsKoyxdgDCXJKSgwS2bMOQZT0I00= +gorm.io/gen v0.3.26 h1:sFf1j7vNStimPRRAtH4zz5NiHM+1dr6eA9aaRdplyhY= +gorm.io/gen v0.3.26/go.mod h1:a5lq5y3w4g5LMxBcw0wnO6tYUCdNutWODq5LrIt75LE= +gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +gorm.io/hints v1.1.2 h1:b5j0kwk5p4+3BtDtYqqfY+ATSxjj+6ptPgVveuynn9o= +gorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg= +gorm.io/plugin/dbresolver v1.5.3 h1:wFwINGZZmttuu9h7XpvbDHd8Lf9bb8GNzp/NpAMV2wU= +gorm.io/plugin/dbresolver v1.5.3/go.mod h1:TSrVhaUg2DZAWP3PrHlDlITEJmNOkL0tFTjvTEsQ4XE= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/sqlite v1.34.2 h1:J9n76TPsfYYkFkZ9Uy1QphILYifiVEwwOT7yP5b++2Y= +modernc.org/sqlite v1.34.2/go.mod h1:dnR723UrTtjKpoHCAMN0Q/gZ9MT4r+iRvIBb9umWFkU= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +moul.io/zapgorm2 v1.3.0 h1:+CzUTMIcnafd0d/BvBce8T4uPn6DQnpIrz64cyixlkk= +moul.io/zapgorm2 v1.3.0/go.mod h1:nPVy6U9goFKHR4s+zfSo1xVFaoU7Qgd5DoCdOfzoCqs= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/hack/gorm-gen/.gitignore b/hack/gorm-gen/.gitignore new file mode 100644 index 0000000..653f39f --- /dev/null +++ b/hack/gorm-gen/.gitignore @@ -0,0 +1 @@ +dao \ No newline at end of file diff --git a/hack/gorm-gen/gorm.go b/hack/gorm-gen/gorm.go new file mode 100644 index 0000000..46a0f4e --- /dev/null +++ b/hack/gorm-gen/gorm.go @@ -0,0 +1,35 @@ +package main + +import ( + "leafdev.top/Leaf/leaf-library-3/internal/entity" + + "gorm.io/gen" +) + +// Dynamic SQL +//type Querier interface { +// // SELECT * FROM @@table WHERE name = @name{{if role !=""}} AND role = @role{{end}} +// FilterWithNameAndRole(name, role string) ([]gen.T, error) +//} + +func main() { + //app, err := cmd.CreateApp() + //if err != nil { + // panic(err) + //} + g := gen.NewGenerator(gen.Config{ + OutPath: "../../internal/dao", + Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface, // generate mode + }) + + //g.UseDB(app.GORM) + + g.ApplyBasic( + entity.User{}, + ) + + // Generate Type Safe API with Dynamic SQL defined on Querier interface for `model.User` and `model.Company` + //g.ApplyInterface(func(Querier) {}, model.User{}, model.Company{}) + + g.Execute() +} diff --git a/hack/rename/rename.go b/hack/rename/rename.go new file mode 100644 index 0000000..bc48f1e --- /dev/null +++ b/hack/rename/rename.go @@ -0,0 +1,135 @@ +package main + +import ( + "bufio" + "fmt" + "io/fs" + "os" + "os/exec" + "path/filepath" + "strings" +) + +const frameworkModuleName = "leafdev.top/Leaf/leaf-library-3" + +func main() { + // 输入新的 go.mod module + var newModName string + fmt.Printf("Enter new module name(eg: github.com//): ") + _, err := fmt.Scanln(&newModName) + if err != nil { + fmt.Printf("Unable get new module name: %v\n", err) + os.Exit(1) + return + } + + fmt.Printf("Do you want to setup the project to %s? (y/n)", newModName) + var answer string + _, err = fmt.Scanln(&answer) + if err != nil { + fmt.Printf("Error reading user input: %v\n", err) + os.Exit(1) + } + if answer != "y" { + fmt.Printf("Aborting setup.\n") + } + + // 修改 go.mod 文件中的 module 名称 + err = replaceInFile("../../go.mod", frameworkModuleName, newModName) + if err != nil { + fmt.Printf("Error replacing module name in go.mod: %v\n", err) + os.Exit(1) + } + + // 读取 go.mod 中的 module 名称 + modName, err := getModuleName("../../go.mod") + if err != nil { + fmt.Printf("Error reading go.mod: %v\n", err) + os.Exit(1) + } + + if modName == frameworkModuleName { + fmt.Printf("Please update go.mod module to a different name.\n") + os.Exit(1) + } + + fmt.Printf("Module name found: %s\n", modName) + // 遍历当前文件夹(排除 vendor、setup.go 和版本控制文件夹) + err = filepath.Walk("../../", func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + + // 条件排除 + if info.IsDir() && (info.Name() == "vendor" || info.Name() == ".git") { + return filepath.SkipDir + } + if !info.IsDir() && info.Name() == "setup.go" { + return nil + } + + // 处理文件 + if !info.IsDir() { + err := replaceInFile(path, `"`+frameworkModuleName, fmt.Sprintf(`"%s`, modName)) + if err != nil { + fmt.Printf("Error replacing in file %s: %v\n", path, err) + } + } + + return nil + }) + if err != nil { + fmt.Printf("Error walking the path: %v\n", err) + } + + // run go mod tidy + fmt.Println("Running go mod tidy...") + var aCmd = exec.Command("go", "mod", "tidy") + if err := aCmd.Run(); err != nil { + fmt.Printf("Error running go mod tidy: %v\n", err) + } + +} + +// 读取 go.mod 文件中的 module 名称 +func getModuleName(modFilePath string) (string, error) { + file, err := os.Open(modFilePath) + if err != nil { + return "", err + } + defer func(file *os.File) { + err := file.Close() + if err != nil { + fmt.Printf("Error closing file: %v\n", err) + } + }(file) + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "module ") { + return strings.TrimSpace(strings.TrimPrefix(line, "module ")), nil + } + } + + if err := scanner.Err(); err != nil { + return "", err + } + + return "", fmt.Errorf("module name not found in go.mod") +} + +// 在文件中替换指定的字符串 +func replaceInFile(filePath string, old string, new string) error { + input, err := os.ReadFile(filePath) + if err != nil { + return err + } + + output := strings.ReplaceAll(string(input), old, new) + if err = os.WriteFile(filePath, []byte(output), 0666); err != nil { + return err + } + + return nil +} diff --git a/internal/api/api.go b/internal/api/api.go new file mode 100644 index 0000000..0d9905f --- /dev/null +++ b/internal/api/api.go @@ -0,0 +1,28 @@ +package api + +import ( + "github.com/google/wire" + "leafdev.top/Leaf/leaf-library-3/internal/api/grpc" + "leafdev.top/Leaf/leaf-library-3/internal/api/http" +) + +var Provide = wire.NewSet( + grpc.ProviderSet, + http.ProviderSet, + NewApi, +) + +type Api struct { + GRPC *grpc.Handlers + HTTP *http.Handlers +} + +func NewApi( + grpcHandlers *grpc.Handlers, + httpHandlers *http.Handlers, +) *Api { + return &Api{ + GRPC: grpcHandlers, + HTTP: httpHandlers, + } +} diff --git a/internal/api/grpc/interceptor/auth.go b/internal/api/grpc/interceptor/auth.go new file mode 100644 index 0000000..7d996a4 --- /dev/null +++ b/internal/api/grpc/interceptor/auth.go @@ -0,0 +1,149 @@ +package interceptor + +import ( + "context" + + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" + "google.golang.org/grpc" + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" + "leafdev.top/Leaf/leaf-library-3/internal/base/logger" + "leafdev.top/Leaf/leaf-library-3/internal/consts" + "leafdev.top/Leaf/leaf-library-3/internal/schema" + auth2 "leafdev.top/Leaf/leaf-library-3/internal/service/auth" +) + +var ignoreAuthApis = map[string]bool{ + // 反射 + "/grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo": true, + + // 业务 API + "/api.v1.WorkspaceService/List": true, +} + +type Auth struct { + authService *auth2.Service + logger *logger.Logger + config *conf.Config +} + +func NewAuth( + authService *auth2.Service, + logger *logger.Logger, + config *conf.Config, +) *Auth { + return &Auth{ + authService: authService, + logger: logger, + config: config, + } +} + +func (a *Auth) notRequireAuth(fullMethodName string) bool { + var b = ignoreAuthApis[fullMethodName] + + if a.config.Debug.Enabled { + if b { + a.logger.Sugar.Debugf("[GRPC Auth] Ignore auth for Method: %s", fullMethodName) + } else { + a.logger.Sugar.Debugf("[GRPC Auth] Require auth for Method: %s", fullMethodName) + } + + } + + return b +} + +func (a *Auth) authCtx(ctx context.Context) (context.Context, error) { + var tokenString string + var err error + + tokenString, err = auth.AuthFromMD(ctx, "bearer") + if err != nil { + // 如果是调试模式,就不处理报错,并且继续执行 + if a.config.Debug.Enabled { + tokenString = "" + a.logger.Sugar.Debugf("[GRPC Auth] error, %s", err) + } else { + return nil, err + } + } + + token, err := a.authService.AuthFromToken(schema.JWTIDToken, tokenString) + if err != nil { + return nil, err + } + + if !token.Valid { + return nil, consts.ErrNotValidToken + } + + ctx = logging.InjectFields(ctx, logging.Fields{consts.AuthMiddlewareKey, token.Token.Sub}) + ctx = context.WithValue(ctx, consts.AuthMiddlewareKey, token) + + return ctx, nil +} + +func (a *Auth) UnaryJWTAuth() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + if a.notRequireAuth(info.FullMethod) { + return handler(ctx, req) + } + + ctx, err := a.authCtx(ctx) + + if err != nil { + return nil, err + } + + result, err := handler(ctx, req) + if err != nil { + return nil, err + } + + return result, err + } +} + +func (a *Auth) StreamJWTAuth() grpc.StreamServerInterceptor { + return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + var ctx = ss.Context() + + if a.notRequireAuth(info.FullMethod) { + return handler(srv, ss) + } + ctx, err := a.authCtx(ctx) + + if err != nil { + return err + } + + err = handler(srv, ss) + if err != nil { + return err + } + + return nil + } +} + +// +//func (a *Auth) JwtAuth(ctx context.Context) (context.Context, error) { +// tokenString, err := auth.AuthFromMD(ctx, "bearer") +// if err != nil { +// return nil, err +// } +// +// token, err := a.authService.AuthFromToken(schema.JWTIDToken, tokenString) +// if err != nil { +// return nil, err +// } +// +// if !token.Valid { +// return nil, consts.ErrNotValidToken +// } +// +// ctx = logging.InjectFields(ctx, logging.Fields{consts.AuthMiddlewareKey, token.Token.Sub}) +// +// return context.WithValue(ctx, consts.AuthMiddlewareKey, token), nil +//} diff --git a/internal/api/grpc/interceptor/zap_logger.go b/internal/api/grpc/interceptor/zap_logger.go new file mode 100644 index 0000000..307f1e6 --- /dev/null +++ b/internal/api/grpc/interceptor/zap_logger.go @@ -0,0 +1,57 @@ +package interceptor + +import ( + "context" + "fmt" + + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" + "go.uber.org/zap" + "leafdev.top/Leaf/leaf-library-3/internal/base/logger" +) + +type Logger struct { + Logger *logger.Logger +} + +func NewLogger(logger *logger.Logger) *Logger { + return &Logger{ + Logger: logger, + } +} + +func (l *Logger) ZapLogInterceptor() logging.Logger { + return logging.LoggerFunc(func(ctx context.Context, lvl logging.Level, msg string, fields ...any) { + f := make([]zap.Field, 0, len(fields)/2) + + for i := 0; i < len(fields); i += 2 { + key := fields[i] + value := fields[i+1] + + switch v := value.(type) { + case string: + f = append(f, zap.String(key.(string), v)) + case int: + f = append(f, zap.Int(key.(string), v)) + case bool: + f = append(f, zap.Bool(key.(string), v)) + default: + f = append(f, zap.Any(key.(string), v)) + } + } + + logger2 := l.Logger.Logger.WithOptions(zap.AddCallerSkip(1)).With(f...) + + switch lvl { + case logging.LevelDebug: + logger2.Debug(msg) + case logging.LevelInfo: + logger2.Info(msg) + case logging.LevelWarn: + logger2.Warn(msg) + case logging.LevelError: + logger2.Error(msg) + default: + panic(fmt.Sprintf("unknown level %v", lvl)) + } + }) +} diff --git a/internal/api/grpc/provider.go b/internal/api/grpc/provider.go new file mode 100644 index 0000000..7243a3a --- /dev/null +++ b/internal/api/grpc/provider.go @@ -0,0 +1,46 @@ +package grpc + +import ( + "github.com/google/wire" + "leafdev.top/Leaf/leaf-library-3/internal/api/grpc/interceptor" + "leafdev.top/Leaf/leaf-library-3/internal/api/grpc/v1/documents" +) + +var ProviderSet = wire.NewSet( + interceptor.NewAuth, + interceptor.NewLogger, + documents.NewHandler, + + NewInterceptor, + NewHandler, +) + +func NewHandler( + documentApi *documents.Handler, + interceptor2 *Interceptor, +) *Handlers { + return &Handlers{ + DocumentApi: documentApi, + Interceptor: interceptor2, + } +} + +type Handlers struct { + DocumentApi *documents.Handler + Interceptor *Interceptor +} + +type Interceptor struct { + Auth *interceptor.Auth + Logger *interceptor.Logger +} + +func NewInterceptor( + Auth *interceptor.Auth, + Logger *interceptor.Logger, +) *Interceptor { + return &Interceptor{ + Auth: Auth, + Logger: Logger, + } +} diff --git a/internal/api/grpc/v1/documents/document.go b/internal/api/grpc/v1/documents/document.go new file mode 100644 index 0000000..67bfffb --- /dev/null +++ b/internal/api/grpc/v1/documents/document.go @@ -0,0 +1,11 @@ +package documents + +import ( + "context" + + v1 "leafdev.top/Leaf/leaf-library-3/proto/gen/proto/api/v1" +) + +func (d *Handler) ListDocuments(ctx context.Context, req *v1.ListDocumentsRequest) (*v1.ListDocumentsResponse, error) { + return &v1.ListDocumentsResponse{}, nil +} diff --git a/internal/api/grpc/v1/documents/provider.go b/internal/api/grpc/v1/documents/provider.go new file mode 100644 index 0000000..82ffcb1 --- /dev/null +++ b/internal/api/grpc/v1/documents/provider.go @@ -0,0 +1,18 @@ +package documents + +import ( + "leafdev.top/Leaf/leaf-library-3/internal/dao" + v1 "leafdev.top/Leaf/leaf-library-3/proto/gen/proto/api/v1" +) + +type Handler struct { + v1.UnimplementedDocumentServiceServer + dao *dao.Query +} + +func NewHandler(dao *dao.Query) *Handler { + return &Handler{ + v1.UnimplementedDocumentServiceServer{}, + dao, + } +} diff --git a/internal/api/http/middleware/auth.go b/internal/api/http/middleware/auth.go new file mode 100644 index 0000000..d1ace79 --- /dev/null +++ b/internal/api/http/middleware/auth.go @@ -0,0 +1,87 @@ +package middleware + +import ( + "net/http" + "slices" + "strings" + + "github.com/gofiber/fiber/v2" + "leafdev.top/Leaf/leaf-library-3/internal/api/http/response" + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" + "leafdev.top/Leaf/leaf-library-3/internal/consts" + "leafdev.top/Leaf/leaf-library-3/internal/pkg/user" + "leafdev.top/Leaf/leaf-library-3/internal/schema" + "leafdev.top/Leaf/leaf-library-3/internal/service/auth" +) + +type Auth struct { + config *conf.Config + authService *auth.Service +} + +var audienceLength int + +func NewAuth(config *conf.Config, authService *auth.Service) *Auth { + audienceLength = len(config.App.AllowedAudiences) + + return &Auth{ + config, + authService, + } +} + +func (a *Auth) Handler() fiber.Handler { + return func(c *fiber.Ctx) error { + var r = response.Ctx(c) + var err error + var token = new(user.User) + + if a.config.Debug.Enabled { + token, err = a.authService.AuthFromToken(schema.JWTAccessToken, "") + if err != nil { + return r.Error(err).Send() + } + + c.Locals(consts.AuthMiddlewareKey, token) + + return c.Next() + } + + authorization := c.Get(consts.AuthHeader) + + if authorization == "" { + return r.Error(consts.ErrJWTFormatError).Send() + } + + authSplit := strings.Split(authorization, " ") + if len(authSplit) != 2 { + return r.Error(consts.ErrJWTFormatError).Send() + } + + if authSplit[0] != consts.AuthPrefix { + return r.Error(consts.ErrNotBearerType).Send() + } + + token, err = a.authService.AuthFromToken(schema.JWTIDToken, authSplit[1]) + + if err != nil { + return r.Error(err).Status(http.StatusUnauthorized).Send() + } + + if token == nil { + return r.Error(err).Status(http.StatusUnauthorized).Send() + } + + if audienceLength > 0 { + // 检测 aud + if !slices.Contains(a.config.App.AllowedAudiences, token.Token.Aud) { + return r.Error(consts.ErrNotValidToken).Send() + } + } + + c.Locals(consts.AuthMiddlewareKey, token) + + return c.Next() + } + +} diff --git a/internal/api/http/middleware/json_response.go b/internal/api/http/middleware/json_response.go new file mode 100644 index 0000000..21e0cbb --- /dev/null +++ b/internal/api/http/middleware/json_response.go @@ -0,0 +1,20 @@ +package middleware + +import ( + "github.com/gofiber/fiber/v2" +) + +type JSONResponse struct { +} + +func (*JSONResponse) Handler() fiber.Handler { + return func(c *fiber.Ctx) error { + c.Response().Header.Set("Content-Type", "application/json") + return c.Next() + + } +} + +func NewJSONResponse() *JSONResponse { + return &JSONResponse{} +} diff --git a/internal/api/http/middleware/rbac.go b/internal/api/http/middleware/rbac.go new file mode 100644 index 0000000..8b33042 --- /dev/null +++ b/internal/api/http/middleware/rbac.go @@ -0,0 +1,140 @@ +package middleware + +import ( + "fmt" + "net/http" + "strings" + + "github.com/gofiber/fiber/v2" + "leafdev.top/Leaf/leaf-library-3/internal/api/http/response" + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" + userPkg "leafdev.top/Leaf/leaf-library-3/internal/pkg/user" + "leafdev.top/Leaf/leaf-library-3/internal/service/auth" +) + +type RBAC struct { + authService *auth.Service + config *conf.Config +} + +func (m *RBAC) RoutePermission() fiber.Handler { + return func(c *fiber.Ctx) error { + user, ok := m.authService.GetUserSafe(c) + if !ok { + return response.Ctx(c).Error(nil).Status(http.StatusUnauthorized).Send() + } + + if !user.Valid { + return response.Ctx(c).Error(nil).Status(http.StatusUnauthorized).Send() + } + + var path = cleanPath(c.Path()) + act := strings.ToLower(c.Method()) + + permissionName := userPkg.Permission(m.config.App.Name + ":" + path + ":" + act) + pass := user.HasPermissions(permissionName) + + if !pass { + return response.Ctx(c). + Message(fmt.Sprintf("permission denied, permission name: %s", permissionName)). + Error(nil). + Status(http.StatusForbidden). + Send() + } + + return c.Next() + } +} + +func (m *RBAC) RequirePermissions(permissions ...string) fiber.Handler { + return func(c *fiber.Ctx) error { + user, ok := m.authService.GetUserSafe(c) + if !ok { + return response.Ctx(c).Error(nil).Status(http.StatusUnauthorized).Send() + } + + if !user.Valid { + return response.Ctx(c).Error(nil).Status(http.StatusUnauthorized).Send() + } + + var pass = true + var failedPermissionName string + + for _, permission := range permissions { + permissionName := userPkg.Permission(m.config.App.Name + ":" + permission) + + has := user.HasPermissions(permissionName) + if !has { + failedPermissionName = permissionName.String() + pass = false + break + } + } + + if !pass { + return response.Ctx(c). + Message(fmt.Sprintf("permission denied, required permissions: %s, failed permission: %s", + permissions, failedPermissionName)). + Error(nil). + Status(http.StatusForbidden). + Send() + } + + return c.Next() + } +} + +func (m *RBAC) RequireRoles(roles ...string) fiber.Handler { + return func(c *fiber.Ctx) error { + user, ok := m.authService.GetUserSafe(c) + if !ok { + return response.Ctx(c).Error(nil).Status(http.StatusUnauthorized).Send() + } + + if !user.Valid { + return response.Ctx(c).Error(nil).Status(http.StatusUnauthorized).Send() + } + + var pass = true + var failedRoleName string + + for _, role := range roles { + roleName := userPkg.Role(m.config.App.Name + ":" + role) + pass = user.HasRoles(roleName) + + if !pass { + failedRoleName = roleName.String() + break + } + + } + + if !pass { + return response.Ctx(c). + Message(fmt.Sprintf("permission denied, required roles: %s, failed role %s", roles, failedRoleName)). + Error(nil). + Status(http.StatusForbidden). + Send() + } + + return c.Next() + } +} + +func NewRBAC(authService *auth.Service, config *conf.Config) *RBAC { + return &RBAC{ + authService: authService, + config: config, + } +} + +func cleanPath(path string) string { + // 如果第一个字符是 /,则删掉 + if path[0] == '/' { + path = path[1:] + } + + // 将所有的 / 转为 : + return strings.ReplaceAll(path, "/", ":") + +} diff --git a/internal/api/http/middleware/zap_logger.go b/internal/api/http/middleware/zap_logger.go new file mode 100644 index 0000000..6229fe7 --- /dev/null +++ b/internal/api/http/middleware/zap_logger.go @@ -0,0 +1,25 @@ +package middleware + +import ( + "github.com/gofiber/contrib/fiberzap/v2" + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" +) + +type Logger struct { + logger *zap.Logger +} + +func NewLogger(logger *zap.Logger) *Logger { + return &Logger{ + logger: logger, + } +} + +func (l *Logger) Handler() fiber.Handler { + var config = fiberzap.Config{ + Logger: l.logger, + } + + return fiberzap.New(config) +} diff --git a/internal/api/http/provider.go b/internal/api/http/provider.go new file mode 100644 index 0000000..38702a7 --- /dev/null +++ b/internal/api/http/provider.go @@ -0,0 +1,58 @@ +package http + +import ( + "github.com/gofiber/fiber/v2" + "github.com/google/wire" + "leafdev.top/Leaf/leaf-library-3/internal/api/http/middleware" + v1 "leafdev.top/Leaf/leaf-library-3/internal/api/http/v1" + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" + "leafdev.top/Leaf/leaf-library-3/internal/base/logger" + "leafdev.top/Leaf/leaf-library-3/internal/service/auth" +) + +type IMiddleware interface { + Handler() fiber.Handler +} + +type Middleware struct { + Logger IMiddleware + Auth IMiddleware + JSONResponse IMiddleware + RBAC *middleware.RBAC +} + +type Handlers struct { + User *v1.UserController +} + +func NewHandler( + user *v1.UserController, +) *Handlers { + return &Handlers{ + User: user, + } +} + +func NewMiddleware(config *conf.Config, logger *logger.Logger, authService *auth.Service) *Middleware { + return &Middleware{ + Logger: middleware.NewLogger(logger.Logger), + Auth: middleware.NewAuth(config, authService), + JSONResponse: middleware.NewJSONResponse(), + RBAC: middleware.NewRBAC(authService, config), + } +} + +var ProviderSet = wire.NewSet( + // Init Middleware + middleware.NewAuth, + middleware.NewLogger, + middleware.NewJSONResponse, + middleware.NewRBAC, + NewMiddleware, + + // Init Controller + v1.NewUserController, + + // Init Handler + NewHandler, +) diff --git a/internal/api/http/response/http.go b/internal/api/http/response/http.go new file mode 100644 index 0000000..eeb0aaf --- /dev/null +++ b/internal/api/http/response/http.go @@ -0,0 +1,134 @@ +package response + +import ( + "github.com/gofiber/fiber/v2" + "net/http" +) + +type Body struct { + Message string `json:"message"` + Error string `json:"error"` + Success bool `json:"success"` + Data any `json:"data,omitempty"` + Wrap bool `json:"-"` +} + +type HttpResponse struct { + body *Body + httpStatus int + ctx *fiber.Ctx +} + +func Ctx(c *fiber.Ctx) *HttpResponse { + return &HttpResponse{ + body: &Body{ + Wrap: true, + }, + ctx: c, + httpStatus: 0, + } +} + +func (r *HttpResponse) Message(message string) *HttpResponse { + r.body.Message = message + + if r.httpStatus == 0 { + r.httpStatus = http.StatusOK + } + + return r +} + +// WithoutWrap 将不在 body 中包裹 data +func (r *HttpResponse) WithoutWrap() *HttpResponse { + r.body.Wrap = false + + return r +} + +func (r *HttpResponse) Wrap() *HttpResponse { + r.body.Wrap = true + return r +} + +func (r *HttpResponse) Data(data any) *HttpResponse { + r.body.Data = data + return r + +} + +func (r *HttpResponse) Error(err error) *HttpResponse { + if err != nil { + var errMsg = err.Error() + + if errMsg == "EOF" { + errMsg = "Request body is empty or missing some fields, make sure you have provided all the required fields" + } + + r.body.Error = errMsg + + if r.httpStatus == 0 { + r.httpStatus = http.StatusBadRequest + } + + if r.body.Message == "" { + r.Message("Something went wrong") + } + + r.body.Success = false + } + + return r + +} + +func (r *HttpResponse) Status(status int) *HttpResponse { + r.httpStatus = status + return r + +} + +func (r *HttpResponse) Send() error { + if r.httpStatus == 0 { + r.httpStatus = http.StatusOK + } + + // if 20x or 20x, set success + r.body.Success = r.httpStatus >= http.StatusOK && r.httpStatus < http.StatusMultipleChoices + + // if 403 or 401 but not have message + if r.httpStatus == http.StatusForbidden || r.httpStatus == http.StatusUnauthorized { + if r.body.Message == "" { + r.Message("Unauthorized") + } + } + + if r.body.Wrap { + return r.ctx.Status(r.httpStatus).JSON(r.body) + } + + return r.ctx.Status(r.httpStatus).JSON(r.body.Data) +} + +// +//func ResponseMessage(c *gin.Context, code int, message string, data interface{}) { +// c.JSON(code, &Body{ +// Message: message, +// Data: data, +// }) +// c.Abort() +//} +// +//func ResponseError(c *gin.Context, code int, err error) { +// c.JSON(code, &Body{ +// Error: err.Error(), +// }) +// c.Abort() +//} +// +//func Response(c *gin.Context, code int, data interface{}) { +// c.JSON(code, &Body{ +// Data: data, +// }) +// c.Abort() +//} diff --git a/internal/api/http/v1/test.go b/internal/api/http/v1/test.go new file mode 100644 index 0000000..8c1d3cc --- /dev/null +++ b/internal/api/http/v1/test.go @@ -0,0 +1,43 @@ +package v1 + +import ( + "net/http" + + "github.com/gofiber/fiber/v2" + "leafdev.top/Leaf/leaf-library-3/internal/api/http/response" + "leafdev.top/Leaf/leaf-library-3/internal/schema" + "leafdev.top/Leaf/leaf-library-3/internal/service/auth" +) + +type UserController struct { + authService *auth.Service +} + +func NewUserController(authService *auth.Service) *UserController { + return &UserController{authService} +} + +// Test godoc +// @Summary Greet +// @Description 测试接口,将会返回当前用户的信息 +// @Tags ping +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @deprecated true +// @Success 200 {object} response.Body{data=schema.CurrentUserResponse} +// @Failure 400 {object} response.Body +// @Router /api/v1/ping [get] +func (u *UserController) Test(c *fiber.Ctx) error { + user := u.authService.GetUser(c) + + var currentUserResponse = &schema.CurrentUserResponse{ + IP: c.IP(), + Valid: user.Valid, + UserEmail: user.Token.Email, + UserId: user.Token.Sub, + UserName: user.Token.Name, + } + + return response.Ctx(c).Status(http.StatusOK).Data(currentUserResponse).Send() +} diff --git a/internal/base/app.go b/internal/base/app.go new file mode 100644 index 0000000..1c73233 --- /dev/null +++ b/internal/base/app.go @@ -0,0 +1,57 @@ +package base + +import ( + "github.com/milvus-io/milvus-sdk-go/v2/client" + "gorm.io/gorm" + "leafdev.top/Leaf/leaf-library-3/internal/api" + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" + "leafdev.top/Leaf/leaf-library-3/internal/base/logger" + "leafdev.top/Leaf/leaf-library-3/internal/base/redis" + "leafdev.top/Leaf/leaf-library-3/internal/base/s3" + "leafdev.top/Leaf/leaf-library-3/internal/base/server" + "leafdev.top/Leaf/leaf-library-3/internal/batch" + "leafdev.top/Leaf/leaf-library-3/internal/dao" + "leafdev.top/Leaf/leaf-library-3/internal/service" +) + +type Application struct { + Config *conf.Config + Logger *logger.Logger + Api *api.Api + HttpServer *server.HttpServer + GORM *gorm.DB + DAO *dao.Query + Service *service.Service + Redis *redis.Redis + Batch *batch.Batch + S3 *s3.S3 + Milvus client.Client +} + +func NewApplication( + config *conf.Config, + httpServer *server.HttpServer, + api *api.Api, + logger *logger.Logger, + services *service.Service, + redis *redis.Redis, + batch *batch.Batch, + s3 *s3.S3, + gorm *gorm.DB, + dao *dao.Query, + milvus client.Client, +) *Application { + return &Application{ + Config: config, + HttpServer: httpServer, + Api: api, + Logger: logger, + Service: services, + Redis: redis, + Batch: batch, + S3: s3, + GORM: gorm, + DAO: dao, + Milvus: milvus, + } +} diff --git a/internal/base/conf/conf.go b/internal/base/conf/conf.go new file mode 100644 index 0000000..5fe016a --- /dev/null +++ b/internal/base/conf/conf.go @@ -0,0 +1,116 @@ +package conf + +// Config 配置文件不能有下划线或横线,否则不能解析 +type Config struct { + App *App `yaml:"app"` + + Http *Http `yaml:"http"` + + Grpc *Grpc `yaml:"grpc"` + + Debug *Debug `yaml:"debug"` + + Database *Database `yaml:"database"` + + Redis *Redis `yaml:"redis"` + + JWKS *JWKS `yaml:"jwks"` + + Metrics *Metrics `yaml:"metrics"` + + S3 *S3 `yaml:"s3"` + + Kafka *Kafka `yaml:"kafka"` + + Milvus *Milvus `yaml:"milvus"` + + ThirdParty *ThirdParty `yaml:"third_party" mapstructure:"third_party"` +} + +type App struct { + Name string `yaml:"name"` + AllowedAudiences []string `yaml:"allowed_audiences" mapstructure:"allowed_audiences"` +} + +type ThirdParty struct { + OpenAIApiKey string `yaml:"openai_api_key" mapstructure:"openai_api_key"` +} + +type Http struct { + Host string `yaml:"host" mapstructure:"host"` + Port int `yaml:"port" mapstructure:"port"` + Url string `yaml:"url" mapstructure:"url"` + Cors struct { + Enabled bool `yaml:"enabled" mapstructure:"enabled"` + AllowedOrigins []string `yaml:"allow_origins" mapstructure:"allow_origins"` + AllowMethods []string `yaml:"allow_methods" mapstructure:"allow_methods"` + AllowHeaders []string `yaml:"allow_headers" mapstructure:"allow_headers"` + AllowCredentials bool `yaml:"allow_credentials" mapstructure:"allow_credentials"` + ExposeHeaders []string `yaml:"expose_headers" mapstructure:"expose_headers"` + MaxAge int `yaml:"max_age" mapstructure:"max_age"` + } `yaml:"cors" mapstructure:"cors"` +} + +type Debug struct { + Enabled bool `yaml:"enabled"` +} + +type Database struct { + Host string `yaml:"host"` + Port int `yaml:"port"` + User string `yaml:"user"` + Password string `yaml:"password"` + Name string `yaml:"name"` + SSLMode string `yaml:"sslmode"` + TimeZone string `yaml:"timezone"` +} + +type Redis struct { + Host string `yaml:"host"` + Port int `yaml:"port"` + Password string `yaml:"password"` + DB int `yaml:"db"` +} + +type JWKS struct { + Url string `yaml:"url"` +} + +type Metrics struct { + Enabled bool `yaml:"enabled"` + Port int `yaml:"port"` + Host string `yaml:"host"` +} + +type S3 struct { + Endpoint string `yaml:"endpoint" mapstructure:"endpoint"` + ExternalEndpoint string `yaml:"external_endpoint" mapstructure:"external_endpoint"` + AccessKey string `yaml:"access_key" mapstructure:"access_key"` + SecretKey string `yaml:"secret_key" mapstructure:"secret_key"` + Bucket string `yaml:"bucket" mapstructure:"bucket"` + UseSSL bool `yaml:"use_ssl" mapstructure:"use_ssl"` + Region string `yaml:"region" mapstructure:"region"` +} + +type Kafka struct { + BootstrapServers []string `yaml:"bootstrap_servers" mapstructure:"bootstrap_servers"` + GroupId string `yaml:"group_id" mapstructure:"group_id"` + Username string `yaml:"username" mapstructure:"username"` + Password string `yaml:"password" mapstructure:"password"` + MainTopic string `yaml:"main_topic" mapstructure:"main_topic"` + WorkerTopic string `yaml:"worker_topic" mapstructure:"worker_topic"` +} + +type Milvus struct { + Host string `yaml:"host" mapstructure:"host"` + Port int `yaml:"port" mapstructure:"port"` + DBName string `yaml:"db_name" mapstructure:"db_name"` + DocumentCollection string `yaml:"document_collection" mapstructure:"document_collection"` + User string `yaml:"user" mapstructure:"user"` + Password string `yaml:"password" mapstructure:"password"` +} + +type Grpc struct { + Address string `yaml:"address" mapstructure:"address"` + AddressGateway string `yaml:"address_gateway" mapstructure:"address_gateway"` +} diff --git a/internal/base/conf/helper.go b/internal/base/conf/helper.go new file mode 100644 index 0000000..90ad92d --- /dev/null +++ b/internal/base/conf/helper.go @@ -0,0 +1,36 @@ +package conf + +import ( + "os" + + "leafdev.top/Leaf/leaf-library-3/configs" +) + +func createConfigIfNotExists(path string) { + if path != "" { + return + } + + // create if not exists + var configName = "config.yaml" + + if _, err := os.Stat(configName); os.IsNotExist(err) { + f, err := os.Create(configName) + if err != nil { + panic(err) + } + + // write default from embed + _, err = f.Write(configs.Config) + if err != nil { + panic(err) + } + + defer func(f *os.File) { + err := f.Close() + if err != nil { + panic(err) + } + }(f) + } +} diff --git a/internal/base/conf/provider.go b/internal/base/conf/provider.go new file mode 100644 index 0000000..a67223f --- /dev/null +++ b/internal/base/conf/provider.go @@ -0,0 +1,70 @@ +package conf + +import ( + "os" + "path/filepath" + "strings" + + "github.com/spf13/viper" +) + +// depth 是配置文件的搜索深度 +var depth = 8 + +func getConfigPath() string { + var path string + if os.Getenv("CONFIG") != "" { + path = os.Getenv("CONFIG") + return path + } + var pathOptions []string + for i := 0; i <= depth; i++ { + pathOptions = append(pathOptions, strings.Repeat("../", i)+"config.yaml") + } + for _, p := range pathOptions { + if _, err := os.Stat(p); err == nil { + path = p + break + } + } + + if path != "" { + // 假设 workDir 是当前工作目录的路径 + workDir, err := os.Getwd() + if err != nil { + panic(err) + } + + // 将相对路径转换为绝对路径 + path, err = filepath.Abs(filepath.Join(workDir, path)) + if err != nil { + panic(err) + } + } + + return path +} + +func NewConfig() *Config { + var path = getConfigPath() + createConfigIfNotExists(path) + + if path == "" { + panic("config file not found, created on app root.") + } else { + println("config file found:", path) + } + + c := &Config{} + v := viper.New() + v.SetConfigType("yaml") + v.SetConfigFile(path) + if err := v.ReadInConfig(); err != nil { + panic(err) + } + if err := v.Unmarshal(c); err != nil { + panic(err) + } + + return c +} diff --git a/internal/base/conf/rbac_model.go b/internal/base/conf/rbac_model.go new file mode 100644 index 0000000..b1ebb19 --- /dev/null +++ b/internal/base/conf/rbac_model.go @@ -0,0 +1,9 @@ +package conf + +import ( + "leafdev.top/Leaf/leaf-library-3/configs" +) + +func (c *Config) GetRBACModel() string { + return string(configs.RBACModel) +} diff --git a/internal/base/logger/provider.go b/internal/base/logger/provider.go new file mode 100644 index 0000000..a1ab68d --- /dev/null +++ b/internal/base/logger/provider.go @@ -0,0 +1,36 @@ +package logger + +import ( + "go.uber.org/zap" + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" +) + +type Logger struct { + Sugar *zap.SugaredLogger + Logger *zap.Logger +} + +func NewZapLogger(config *conf.Config) *Logger { + var logger *zap.Logger + var err error + + if config.Debug.Enabled { + logger, err = zap.NewDevelopment(zap.AddCallerSkip(1)) + } else { + logger, err = zap.NewProduction() + } + + if err != nil { + panic(err) + return nil + } + + //defer func(logger *zap.Logger) { + // err := logger.Sync() + // if err != nil { + // panic(err) + // } + //}(logger) + + return &Logger{Sugar: logger.Sugar(), Logger: logger} +} diff --git a/internal/base/milvus/provide.go b/internal/base/milvus/provide.go new file mode 100644 index 0000000..7285322 --- /dev/null +++ b/internal/base/milvus/provide.go @@ -0,0 +1,30 @@ +package milvus + +import ( + "context" + + "github.com/milvus-io/milvus-sdk-go/v2/client" + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" + "leafdev.top/Leaf/leaf-library-3/internal/base/logger" + + "strconv" +) + +func NewService(config *conf.Config, logger *logger.Logger) client.Client { + var address = config.Milvus.Host + ":" + strconv.Itoa(config.Milvus.Port) + + logger.Sugar.Infof("Connecting Milvus, address=%s, dbname=%s", address, config.Milvus.DBName) + + c, err := client.NewClient(context.Background(), client.Config{ + Address: address, + DBName: config.Milvus.DBName, + }) + + logger.Sugar.Infof("Milvus connected!") + + if err != nil { + panic(err) + } + + return c +} diff --git a/internal/base/orm/provide.go b/internal/base/orm/provide.go new file mode 100644 index 0000000..09bdf7d --- /dev/null +++ b/internal/base/orm/provide.go @@ -0,0 +1,36 @@ +package orm + +import ( + "fmt" + + "gorm.io/driver/postgres" + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" + "leafdev.top/Leaf/leaf-library-3/internal/base/logger" + + "gorm.io/gorm" + "moul.io/zapgorm2" +) + +func NewGORM( + config *conf.Config, + logger *logger.Logger, +) *gorm.DB { + dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=%s", + config.Database.Host, config.Database.User, config.Database.Password, config.Database.Name, config.Database.Port, config.Database.SSLMode, config.Database.TimeZone) + + gormConfig := &gorm.Config{} + + if !config.Debug.Enabled { + zapGormLogger := zapgorm2.New(logger.Logger) + zapGormLogger.SetAsDefault() + gormConfig.Logger = zapGormLogger + } + + db, err := gorm.Open(postgres.Open(dsn), gormConfig) + if err != nil { + panic(err) + } + + return db + +} diff --git a/internal/base/redis/provide.go b/internal/base/redis/provide.go new file mode 100644 index 0000000..12db33a --- /dev/null +++ b/internal/base/redis/provide.go @@ -0,0 +1,39 @@ +package redis + +import ( + "context" + "fmt" + + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" + + "github.com/bsm/redislock" + "github.com/redis/go-redis/v9" +) + +type Redis struct { + Client *redis.Client + Locker *redislock.Client +} + +func NewRedis(c *conf.Config) *Redis { + var client = redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%d", c.Redis.Host, c.Redis.Port), + Password: c.Redis.Password, + DB: c.Redis.DB, + }) + + status := client.Ping(context.Background()) + if status.Err() != nil { + panic(status.Err()) + } + + // Create a new lock client. + locker := redislock.New(client) + + var r = &Redis{ + Client: client, + Locker: locker, + } + + return r +} diff --git a/internal/base/s3/provide.go b/internal/base/s3/provide.go new file mode 100644 index 0000000..acfa5e9 --- /dev/null +++ b/internal/base/s3/provide.go @@ -0,0 +1,29 @@ +package s3 + +import ( + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" + + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" +) + +type S3 struct { + Client *minio.Client + Bucket string +} + +func NewS3(config *conf.Config) *S3 { + minioClient, err := minio.New(config.S3.Endpoint, &minio.Options{ + Creds: credentials.NewStaticV4(config.S3.AccessKey, config.S3.SecretKey, ""), + Secure: config.S3.UseSSL, + }) + + if err != nil { + panic(err) + } + + return &S3{ + Client: minioClient, + Bucket: config.S3.Bucket, + } +} diff --git a/internal/base/server/http.go b/internal/base/server/http.go new file mode 100644 index 0000000..6c2cd3e --- /dev/null +++ b/internal/base/server/http.go @@ -0,0 +1,111 @@ +package server + +import ( + "net/http" + "strings" + + "github.com/ansrivas/fiberprometheus/v2" + "github.com/bytedance/sonic" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v2/middleware/recover" + httpApi "leafdev.top/Leaf/leaf-library-3/internal/api/http" + "leafdev.top/Leaf/leaf-library-3/internal/api/http/response" + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" + "leafdev.top/Leaf/leaf-library-3/internal/base/logger" + "leafdev.top/Leaf/leaf-library-3/internal/consts" + "leafdev.top/Leaf/leaf-library-3/internal/router" + "leafdev.top/Leaf/leaf-library-3/internal/service/auth" +) + +type HttpServer struct { + config *conf.Config + Fiber *fiber.App + apiRouter *router.Api + swaggerRouter *router.SwaggerRouter + middleware *httpApi.Middleware + authService *auth.Service +} + +// NewHTTPServer new http server. +func NewHTTPServer( + config *conf.Config, + apiRouter *router.Api, + swaggerRouter *router.SwaggerRouter, + middleware *httpApi.Middleware, + logger *logger.Logger, +) *HttpServer { + app := fiber.New(fiber.Config{ + JSONEncoder: sonic.Marshal, + JSONDecoder: sonic.Unmarshal, + ErrorHandler: func(ctx *fiber.Ctx, err error) error { + logger.Sugar.Errorf("fiber error: %s", err) + return response.Ctx(ctx).Status(fiber.StatusInternalServerError).Error(consts.ErrInternalServerError).Send() + }, + }) + app.Use(recover.New()) + app.Use(middleware.Logger.Handler()) + app.Use(middleware.JSONResponse.Handler()) + + return &HttpServer{ + config: config, + Fiber: app, + apiRouter: apiRouter, + swaggerRouter: swaggerRouter, + middleware: middleware, + } +} + +func (hs *HttpServer) AllowAllCors() { + if hs.config.Http.Cors.Enabled { + var config = cors.Config{ + AllowOrigins: strings.Join(hs.config.Http.Cors.AllowedOrigins, ","), + AllowMethods: strings.Join(hs.config.Http.Cors.AllowMethods, ","), + AllowHeaders: strings.Join(hs.config.Http.Cors.AllowHeaders, ","), + AllowCredentials: hs.config.Http.Cors.AllowCredentials, + ExposeHeaders: strings.Join(hs.config.Http.Cors.ExposeHeaders, ","), + MaxAge: hs.config.Http.Cors.MaxAge, + } + + hs.Fiber.Use(cors.New(config)) + } +} + +func (hs *HttpServer) BizRouter() *fiber.App { + hs.AllowAllCors() + + rootGroup := hs.Fiber.Group("") + + // Swagger + hs.swaggerRouter.Register(rootGroup) + + // 注册路由 + hs.apiRouter.V1(rootGroup) + + // 404 Route + hs.Fiber.Use(func(ctx *fiber.Ctx) error { + return response.Ctx(ctx).Status(fiber.StatusNotFound).Send() + }) + + return hs.Fiber +} + +func (hs *HttpServer) MetricRouter() *fiber.App { + app := fiber.New() + + app.Use(recover.New()) + + metricGroup := app.Group("") + + prometheus := fiberprometheus.New(hs.config.App.Name) + + prometheus.RegisterAt(app, "/metrics") + + metricGroup.Use(prometheus.Middleware) + + metricGroup.Get("/healthz", func(ctx *fiber.Ctx) error { + return ctx.Status(http.StatusOK).SendString("OK") + }) + + return app +} diff --git a/internal/batch/provider.go b/internal/batch/provider.go new file mode 100644 index 0000000..9f75d4c --- /dev/null +++ b/internal/batch/provider.go @@ -0,0 +1,16 @@ +package batch + +import ( + "leafdev.top/Leaf/leaf-library-3/internal/base/logger" +) + +type Batch struct { + logger *logger.Logger +} + +func NewBatch( + logger *logger.Logger, +) *Batch { + //base.NewApplication() + return &Batch{logger} +} diff --git a/internal/consts/auth.go b/internal/consts/auth.go new file mode 100644 index 0000000..17a8d48 --- /dev/null +++ b/internal/consts/auth.go @@ -0,0 +1,31 @@ +package consts + +import ( + "errors" + + "leafdev.top/Leaf/leaf-library-3/internal/pkg/user" +) + +const ( + AuthHeader = "Authorization" + AuthPrefix = "Bearer" + + // AnonymousUser 调试模式下的用户 + AnonymousUser user.Id = "anonymous" + + AuthMiddlewareKey = "auth.user" + AuthAssistantShareMiddlewareKey = "auth.assistant.share" +) + +var ( + ErrNotValidToken = errors.New("JWT not valid") + ErrJWTFormatError = errors.New("JWT format error") + ErrNotBearerType = errors.New("not bearer token") + ErrEmptyResponse = errors.New("empty response") + ErrTokenError = errors.New("token type error") + ErrUnauthorized = errors.New("unauthorized") + ErrAudienceNotAllowed = errors.New("audience not allowed") + + ErrNotYourResource = errors.New("this resource not yours") + ErrPermissionDenied = errors.New("permission denied") +) diff --git a/internal/consts/model.go b/internal/consts/model.go new file mode 100644 index 0000000..58eeb88 --- /dev/null +++ b/internal/consts/model.go @@ -0,0 +1,7 @@ +package consts + +import "errors" + +var ( + ErrPageNotFound = errors.New("page not found") +) diff --git a/internal/consts/rbac.go b/internal/consts/rbac.go new file mode 100644 index 0000000..e4f1b69 --- /dev/null +++ b/internal/consts/rbac.go @@ -0,0 +1,9 @@ +package consts + +import ( + "leafdev.top/Leaf/leaf-library-3/internal/pkg/user" +) + +const ( + RoleSuperAdmin user.Role = "super-admin" +) diff --git a/internal/consts/router.go b/internal/consts/router.go new file mode 100644 index 0000000..5f13318 --- /dev/null +++ b/internal/consts/router.go @@ -0,0 +1,7 @@ +package consts + +import "errors" + +var ( + ErrInternalServerError = errors.New("there was a server error, but we have logged this request for further investigation") +) diff --git a/internal/dao/gen.go b/internal/dao/gen.go new file mode 100644 index 0000000..13df407 --- /dev/null +++ b/internal/dao/gen.go @@ -0,0 +1,103 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dao + +import ( + "context" + "database/sql" + + "gorm.io/gorm" + + "gorm.io/gen" + + "gorm.io/plugin/dbresolver" +) + +var ( + Q = new(Query) + User *user +) + +func SetDefault(db *gorm.DB, opts ...gen.DOOption) { + *Q = *Use(db, opts...) + User = &Q.User +} + +func Use(db *gorm.DB, opts ...gen.DOOption) *Query { + return &Query{ + db: db, + User: newUser(db, opts...), + } +} + +type Query struct { + db *gorm.DB + + User user +} + +func (q *Query) Available() bool { return q.db != nil } + +func (q *Query) clone(db *gorm.DB) *Query { + return &Query{ + db: db, + User: q.User.clone(db), + } +} + +func (q *Query) ReadDB() *Query { + return q.ReplaceDB(q.db.Clauses(dbresolver.Read)) +} + +func (q *Query) WriteDB() *Query { + return q.ReplaceDB(q.db.Clauses(dbresolver.Write)) +} + +func (q *Query) ReplaceDB(db *gorm.DB) *Query { + return &Query{ + db: db, + User: q.User.replaceDB(db), + } +} + +type queryCtx struct { + User IUserDo +} + +func (q *Query) WithContext(ctx context.Context) *queryCtx { + return &queryCtx{ + User: q.User.WithContext(ctx), + } +} + +func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error { + return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...) +} + +func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx { + tx := q.db.Begin(opts...) + return &QueryTx{Query: q.clone(tx), Error: tx.Error} +} + +type QueryTx struct { + *Query + Error error +} + +func (q *QueryTx) Commit() error { + return q.db.Commit().Error +} + +func (q *QueryTx) Rollback() error { + return q.db.Rollback().Error +} + +func (q *QueryTx) SavePoint(name string) error { + return q.db.SavePoint(name).Error +} + +func (q *QueryTx) RollbackTo(name string) error { + return q.db.RollbackTo(name).Error +} diff --git a/internal/dao/provider.go b/internal/dao/provider.go new file mode 100644 index 0000000..0fadf4b --- /dev/null +++ b/internal/dao/provider.go @@ -0,0 +1,7 @@ +package dao + +import "gorm.io/gorm" + +func NewQuery(db *gorm.DB) *Query { + return Use(db) +} diff --git a/internal/dao/users.gen.go b/internal/dao/users.gen.go new file mode 100644 index 0000000..70c2ea1 --- /dev/null +++ b/internal/dao/users.gen.go @@ -0,0 +1,392 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dao + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "leafdev.top/Leaf/leaf-library-3/internal/entity" +) + +func newUser(db *gorm.DB, opts ...gen.DOOption) user { + _user := user{} + + _user.userDo.UseDB(db, opts...) + _user.userDo.UseModel(&entity.User{}) + + tableName := _user.userDo.TableName() + _user.ALL = field.NewAsterisk(tableName) + _user.Id = field.NewUint(tableName, "id") + _user.CreatedAt = field.NewTime(tableName, "created_at") + _user.UpdatedAt = field.NewTime(tableName, "updated_at") + _user.Name = field.NewString(tableName, "name") + + _user.fillFieldMap() + + return _user +} + +type user struct { + userDo + + ALL field.Asterisk + Id field.Uint + CreatedAt field.Time + UpdatedAt field.Time + Name field.String + + fieldMap map[string]field.Expr +} + +func (u user) Table(newTableName string) *user { + u.userDo.UseTable(newTableName) + return u.updateTableName(newTableName) +} + +func (u user) As(alias string) *user { + u.userDo.DO = *(u.userDo.As(alias).(*gen.DO)) + return u.updateTableName(alias) +} + +func (u *user) updateTableName(table string) *user { + u.ALL = field.NewAsterisk(table) + u.Id = field.NewUint(table, "id") + u.CreatedAt = field.NewTime(table, "created_at") + u.UpdatedAt = field.NewTime(table, "updated_at") + u.Name = field.NewString(table, "name") + + u.fillFieldMap() + + return u +} + +func (u *user) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := u.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (u *user) fillFieldMap() { + u.fieldMap = make(map[string]field.Expr, 4) + u.fieldMap["id"] = u.Id + u.fieldMap["created_at"] = u.CreatedAt + u.fieldMap["updated_at"] = u.UpdatedAt + u.fieldMap["name"] = u.Name +} + +func (u user) clone(db *gorm.DB) user { + u.userDo.ReplaceConnPool(db.Statement.ConnPool) + return u +} + +func (u user) replaceDB(db *gorm.DB) user { + u.userDo.ReplaceDB(db) + return u +} + +type userDo struct{ gen.DO } + +type IUserDo interface { + gen.SubQuery + Debug() IUserDo + WithContext(ctx context.Context) IUserDo + WithResult(fc func(tx gen.Dao)) gen.ResultInfo + ReplaceDB(db *gorm.DB) + ReadDB() IUserDo + WriteDB() IUserDo + As(alias string) gen.Dao + Session(config *gorm.Session) IUserDo + Columns(cols ...field.Expr) gen.Columns + Clauses(conds ...clause.Expression) IUserDo + Not(conds ...gen.Condition) IUserDo + Or(conds ...gen.Condition) IUserDo + Select(conds ...field.Expr) IUserDo + Where(conds ...gen.Condition) IUserDo + Order(conds ...field.Expr) IUserDo + Distinct(cols ...field.Expr) IUserDo + Omit(cols ...field.Expr) IUserDo + Join(table schema.Tabler, on ...field.Expr) IUserDo + LeftJoin(table schema.Tabler, on ...field.Expr) IUserDo + RightJoin(table schema.Tabler, on ...field.Expr) IUserDo + Group(cols ...field.Expr) IUserDo + Having(conds ...gen.Condition) IUserDo + Limit(limit int) IUserDo + Offset(offset int) IUserDo + Count() (count int64, err error) + Scopes(funcs ...func(gen.Dao) gen.Dao) IUserDo + Unscoped() IUserDo + Create(values ...*entity.User) error + CreateInBatches(values []*entity.User, batchSize int) error + Save(values ...*entity.User) error + First() (*entity.User, error) + Take() (*entity.User, error) + Last() (*entity.User, error) + Find() ([]*entity.User, error) + FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.User, err error) + FindInBatches(result *[]*entity.User, batchSize int, fc func(tx gen.Dao, batch int) error) error + Pluck(column field.Expr, dest interface{}) error + Delete(...*entity.User) (info gen.ResultInfo, err error) + Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + Updates(value interface{}) (info gen.ResultInfo, err error) + UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + UpdateColumns(value interface{}) (info gen.ResultInfo, err error) + UpdateFrom(q gen.SubQuery) gen.Dao + Attrs(attrs ...field.AssignExpr) IUserDo + Assign(attrs ...field.AssignExpr) IUserDo + Joins(fields ...field.RelationField) IUserDo + Preload(fields ...field.RelationField) IUserDo + FirstOrInit() (*entity.User, error) + FirstOrCreate() (*entity.User, error) + FindByPage(offset int, limit int) (result []*entity.User, count int64, err error) + ScanByPage(result interface{}, offset int, limit int) (count int64, err error) + Scan(result interface{}) (err error) + Returning(value interface{}, columns ...string) IUserDo + UnderlyingDB() *gorm.DB + schema.Tabler +} + +func (u userDo) Debug() IUserDo { + return u.withDO(u.DO.Debug()) +} + +func (u userDo) WithContext(ctx context.Context) IUserDo { + return u.withDO(u.DO.WithContext(ctx)) +} + +func (u userDo) ReadDB() IUserDo { + return u.Clauses(dbresolver.Read) +} + +func (u userDo) WriteDB() IUserDo { + return u.Clauses(dbresolver.Write) +} + +func (u userDo) Session(config *gorm.Session) IUserDo { + return u.withDO(u.DO.Session(config)) +} + +func (u userDo) Clauses(conds ...clause.Expression) IUserDo { + return u.withDO(u.DO.Clauses(conds...)) +} + +func (u userDo) Returning(value interface{}, columns ...string) IUserDo { + return u.withDO(u.DO.Returning(value, columns...)) +} + +func (u userDo) Not(conds ...gen.Condition) IUserDo { + return u.withDO(u.DO.Not(conds...)) +} + +func (u userDo) Or(conds ...gen.Condition) IUserDo { + return u.withDO(u.DO.Or(conds...)) +} + +func (u userDo) Select(conds ...field.Expr) IUserDo { + return u.withDO(u.DO.Select(conds...)) +} + +func (u userDo) Where(conds ...gen.Condition) IUserDo { + return u.withDO(u.DO.Where(conds...)) +} + +func (u userDo) Order(conds ...field.Expr) IUserDo { + return u.withDO(u.DO.Order(conds...)) +} + +func (u userDo) Distinct(cols ...field.Expr) IUserDo { + return u.withDO(u.DO.Distinct(cols...)) +} + +func (u userDo) Omit(cols ...field.Expr) IUserDo { + return u.withDO(u.DO.Omit(cols...)) +} + +func (u userDo) Join(table schema.Tabler, on ...field.Expr) IUserDo { + return u.withDO(u.DO.Join(table, on...)) +} + +func (u userDo) LeftJoin(table schema.Tabler, on ...field.Expr) IUserDo { + return u.withDO(u.DO.LeftJoin(table, on...)) +} + +func (u userDo) RightJoin(table schema.Tabler, on ...field.Expr) IUserDo { + return u.withDO(u.DO.RightJoin(table, on...)) +} + +func (u userDo) Group(cols ...field.Expr) IUserDo { + return u.withDO(u.DO.Group(cols...)) +} + +func (u userDo) Having(conds ...gen.Condition) IUserDo { + return u.withDO(u.DO.Having(conds...)) +} + +func (u userDo) Limit(limit int) IUserDo { + return u.withDO(u.DO.Limit(limit)) +} + +func (u userDo) Offset(offset int) IUserDo { + return u.withDO(u.DO.Offset(offset)) +} + +func (u userDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IUserDo { + return u.withDO(u.DO.Scopes(funcs...)) +} + +func (u userDo) Unscoped() IUserDo { + return u.withDO(u.DO.Unscoped()) +} + +func (u userDo) Create(values ...*entity.User) error { + if len(values) == 0 { + return nil + } + return u.DO.Create(values) +} + +func (u userDo) CreateInBatches(values []*entity.User, batchSize int) error { + return u.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (u userDo) Save(values ...*entity.User) error { + if len(values) == 0 { + return nil + } + return u.DO.Save(values) +} + +func (u userDo) First() (*entity.User, error) { + if result, err := u.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.User), nil + } +} + +func (u userDo) Take() (*entity.User, error) { + if result, err := u.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.User), nil + } +} + +func (u userDo) Last() (*entity.User, error) { + if result, err := u.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.User), nil + } +} + +func (u userDo) Find() ([]*entity.User, error) { + result, err := u.DO.Find() + return result.([]*entity.User), err +} + +func (u userDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.User, err error) { + buf := make([]*entity.User, 0, batchSize) + err = u.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (u userDo) FindInBatches(result *[]*entity.User, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return u.DO.FindInBatches(result, batchSize, fc) +} + +func (u userDo) Attrs(attrs ...field.AssignExpr) IUserDo { + return u.withDO(u.DO.Attrs(attrs...)) +} + +func (u userDo) Assign(attrs ...field.AssignExpr) IUserDo { + return u.withDO(u.DO.Assign(attrs...)) +} + +func (u userDo) Joins(fields ...field.RelationField) IUserDo { + for _, _f := range fields { + u = *u.withDO(u.DO.Joins(_f)) + } + return &u +} + +func (u userDo) Preload(fields ...field.RelationField) IUserDo { + for _, _f := range fields { + u = *u.withDO(u.DO.Preload(_f)) + } + return &u +} + +func (u userDo) FirstOrInit() (*entity.User, error) { + if result, err := u.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.User), nil + } +} + +func (u userDo) FirstOrCreate() (*entity.User, error) { + if result, err := u.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.User), nil + } +} + +func (u userDo) FindByPage(offset int, limit int) (result []*entity.User, count int64, err error) { + result, err = u.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = u.Offset(-1).Limit(-1).Count() + return +} + +func (u userDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = u.Count() + if err != nil { + return + } + + err = u.Offset(offset).Limit(limit).Scan(result) + return +} + +func (u userDo) Scan(result interface{}) (err error) { + return u.DO.Scan(result) +} + +func (u userDo) Delete(models ...*entity.User) (result gen.ResultInfo, err error) { + return u.DO.Delete(models) +} + +func (u *userDo) withDO(do gen.Dao) *userDo { + u.DO = *do.(*gen.DO) + return u +} diff --git a/internal/database/migrations/1_setup.sql b/internal/database/migrations/1_setup.sql new file mode 100644 index 0000000..b28f3d2 --- /dev/null +++ b/internal/database/migrations/1_setup.sql @@ -0,0 +1,15 @@ +-- +goose Up + +CREATE TABLE IF NOT EXISTS users +( + id BIGSERIAL PRIMARY KEY, + username VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + password VARCHAR(255) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- +goose Down + +DROP TABLE IF EXISTS users; diff --git a/internal/database/migrations/init.go b/internal/database/migrations/init.go new file mode 100644 index 0000000..78d818a --- /dev/null +++ b/internal/database/migrations/init.go @@ -0,0 +1,12 @@ +package migrations + +import ( + "embed" + + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" +) + +//go:embed *.sql +var MigrationFS embed.FS + +var Config *conf.Config diff --git a/internal/entity/Model.go b/internal/entity/Model.go new file mode 100644 index 0000000..7ff105f --- /dev/null +++ b/internal/entity/Model.go @@ -0,0 +1,15 @@ +package entity + +import ( + "time" + + "leafdev.top/Leaf/leaf-library-3/internal/schema" +) + +// Model 是所有 entity 的基类,后期要将所有的 Base 改成这种形式 +type Model struct { + Id schema.EntityId `gorm:"primarykey" json:"id"` + CreatedAt time.Time `gorm:"autoUpdateTime:milli" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime:milli" json:"updated_at"` + //DeletedAt gorm.DeletedAt `gorm:"index"` +} diff --git a/internal/entity/User.go b/internal/entity/User.go new file mode 100644 index 0000000..2b33f90 --- /dev/null +++ b/internal/entity/User.go @@ -0,0 +1,10 @@ +package entity + +type User struct { + Model + Name string `json:"name"` +} + +func (u *User) TableName() string { + return "users" +} diff --git a/internal/pkg/user/user.go b/internal/pkg/user/user.go new file mode 100644 index 0000000..6e4c582 --- /dev/null +++ b/internal/pkg/user/user.go @@ -0,0 +1,114 @@ +package user + +import ( + "slices" + "time" +) + +type Token struct { + Aud string `json:"aud"` + Iss string `json:"iss"` + Iat float64 `json:"iat"` + Exp float64 `json:"exp"` + Sub Id `json:"sub" mapstructure:"-"` + Scopes []string `json:"scopes"` + Roles []Role `json:"roles,omitempty"` + Permissions []Permission `json:"permissions"` + Uuid string `json:"uuid"` + Avatar string `json:"avatar"` + Name string `json:"name"` + EmailVerified bool `json:"email_verified"` + RealNameVerified bool `json:"real_name_verified"` + PhoneVerified bool `json:"phone_verified"` + Email string `json:"email"` + Phone string `json:"phone"` + CreatedAt time.Time `json:"created_at"` +} + +type User struct { + Token Token + Id Id + Valid bool +} + +type Role string + +func (r Role) String() string { + return string(r) +} + +type Permission string + +type Id string + +func (u *User) GetId() Id { + return u.Token.Sub +} + +func (u *User) GetName() string { + return u.Token.Name +} + +func (u *User) GetEmail() string { + return u.Token.Email +} + +func (u *User) GetPhone() string { + return u.Token.Phone +} + +func (u *User) GetRoles() []Role { + return u.Token.Roles +} + +func (u *User) GetPermissions() []Permission { + return u.Token.Permissions +} + +func (u *User) GetAvatar() string { + return u.Token.Avatar +} + +func (u *User) GetUuid() string { + return u.Token.Uuid +} + +func (u *User) HasRoles(roles ...Role) bool { + if len(roles) == 0 { + return true + } + + for _, role := range roles { + pass := slices.Contains(u.Token.Roles, role) + + if !pass { + return false + } + } + + return true +} + +func (u *User) HasPermissions(permissions ...Permission) bool { + if len(permissions) == 0 { + return true + } + + for _, p := range permissions { + pass := slices.Contains(u.Token.Permissions, p) + + if !pass { + return false + } + } + + return true +} + +func (u Id) String() string { + return string(u) +} + +func (up Permission) String() string { + return string(up) +} diff --git a/internal/router/api.go b/internal/router/api.go new file mode 100644 index 0000000..607a986 --- /dev/null +++ b/internal/router/api.go @@ -0,0 +1,48 @@ +package router + +import ( + "github.com/gofiber/fiber/v2" + "leafdev.top/Leaf/leaf-library-3/internal/api/http" +) + +// 两种方法都可以 +//type Api struct { +// User *v1.UserController +//} + +type Api struct { + HttpHandler *http.Handlers + Middleware *http.Middleware +} + +func NewApiRoute( + //User *v1.UserController, + HttpHandler *http.Handlers, + Middleware *http.Middleware, +) *Api { + //return &Api{ + // User, + //} + + return &Api{ + HttpHandler, + Middleware, + } +} + +func (a *Api) V1(r fiber.Router) { + auth := r.Group("/api/v1") + { + // 要求认证 + auth.Use(a.Middleware.Auth.Handler()) + + // RoutePermission 为权限验证 + r.Get("/ping", a.Middleware.RBAC.RoutePermission(), a.HttpHandler.User.Test) + } + + guest := r.Group("/api/v1") + { + guest.Get("/guest_ping", a.HttpHandler.User.Test) + } + +} diff --git a/internal/router/provider.go b/internal/router/provider.go new file mode 100644 index 0000000..07f1552 --- /dev/null +++ b/internal/router/provider.go @@ -0,0 +1,9 @@ +package router + +import "github.com/google/wire" + +// Provide is providers. +var Provide = wire.NewSet( + NewApiRoute, + NewSwaggerRoute, +) diff --git a/internal/router/swagger.go b/internal/router/swagger.go new file mode 100644 index 0000000..59d607d --- /dev/null +++ b/internal/router/swagger.go @@ -0,0 +1,19 @@ +package router + +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/swagger" + _ "leafdev.top/Leaf/leaf-library-3/docs" +) + +type SwaggerRouter struct { + //config *conf.Config +} + +func NewSwaggerRoute() *SwaggerRouter { + return &SwaggerRouter{} +} + +func (a *SwaggerRouter) Register(r fiber.Router) { + r.Get("/swagger/*", swagger.HandlerDefault) +} diff --git a/internal/schema/entity.go b/internal/schema/entity.go new file mode 100644 index 0000000..9cdb9bf --- /dev/null +++ b/internal/schema/entity.go @@ -0,0 +1,23 @@ +package schema + +import ( + "strconv" +) + +type EntityId uint + +func (i EntityId) String() string { + return strconv.FormatUint(uint64(i), 10) +} + +func (i EntityId) Uint() uint { + return uint(i) +} + +func (i EntityId) Uint64() uint64 { + return uint64(i) +} + +func (i EntityId) Int64() int64 { + return int64(i) +} diff --git a/internal/schema/jwt.go b/internal/schema/jwt.go new file mode 100644 index 0000000..8654c65 --- /dev/null +++ b/internal/schema/jwt.go @@ -0,0 +1,12 @@ +package schema + +type JWTTokenTypes string + +const ( + JWTAccessToken JWTTokenTypes = "access_token" + JWTIDToken JWTTokenTypes = "id_token" +) + +func (jwtTokenTypes JWTTokenTypes) String() string { + return string(jwtTokenTypes) +} diff --git a/internal/schema/stream.go b/internal/schema/stream.go new file mode 100644 index 0000000..d554ff2 --- /dev/null +++ b/internal/schema/stream.go @@ -0,0 +1,22 @@ +package schema + +import "encoding/json" + +type EventMessage interface { + JSON() ([]byte, error) +} + +type ProcessPostRequest struct { + EventMessage + PostId string `json:"post_id"` + Content string `json:"content"` +} + +func (p *ProcessPostRequest) JSON() ([]byte, error) { + return json.Marshal(p) +} + +type ProcessPostResult struct { + PostId string `json:"post_id"` + Keywords []string `json:"keywords"` +} diff --git a/internal/schema/user_response.go b/internal/schema/user_response.go new file mode 100644 index 0000000..b1f5edb --- /dev/null +++ b/internal/schema/user_response.go @@ -0,0 +1,11 @@ +package schema + +import "leafdev.top/Leaf/leaf-library-3/internal/pkg/user" + +type CurrentUserResponse struct { + IP string `json:"ip"` + Valid bool `json:"valid"` + UserEmail string `json:"userEmail"` + UserId user.Id `json:"userId"` + UserName string `json:"userName"` +} diff --git a/internal/service/auth/auth.go b/internal/service/auth/auth.go new file mode 100644 index 0000000..454528c --- /dev/null +++ b/internal/service/auth/auth.go @@ -0,0 +1,117 @@ +package auth + +import ( + "context" + + "github.com/gofiber/fiber/v2" + "github.com/mitchellh/mapstructure" + "leafdev.top/Leaf/leaf-library-3/internal/consts" + "leafdev.top/Leaf/leaf-library-3/internal/pkg/user" + "leafdev.top/Leaf/leaf-library-3/internal/schema" +) + +func (a *Service) AuthFromToken(tokenType schema.JWTTokenTypes, token string) (*user.User, error) { + if a.config.Debug.Enabled { + return a.parseUserJWT(tokenType, "") + } + + return a.parseUserJWT(tokenType, token) +} + +func (a *Service) GetUserFromIdToken(idToken string) (*user.User, error) { + return a.parseUserJWT(schema.JWTIDToken, idToken) +} + +func (a *Service) GetUser(ctx *fiber.Ctx) *user.User { + userCtx := ctx.Locals(consts.AuthMiddlewareKey) + + u, ok := userCtx.(*user.User) + u.Id = u.Token.Sub + + if !ok { + panic("User context is not valid") + } + + return u +} + +func (a *Service) GetCtx(ctx context.Context) *user.User { + userCtx := ctx.Value(consts.AuthMiddlewareKey) + + u, ok := userCtx.(*user.User) + u.Id = u.Token.Sub + + if !ok { + panic("User context is not valid") + } + + return u +} + +func (a *Service) GetUserSafe(ctx *fiber.Ctx) (*user.User, bool) { + userCtx := ctx.Locals(consts.AuthMiddlewareKey) + + u, ok := userCtx.(*user.User) + u.Id = u.Token.Sub + + return u, ok +} + +func (a *Service) GetCtxSafe(ctx context.Context) (*user.User, bool) { + userCtx := ctx.Value(consts.AuthMiddlewareKey) + + u, ok := userCtx.(*user.User) + u.Id = u.Token.Sub + + return u, ok +} + +func (a *Service) SetUser(ctx context.Context, user *user.User) context.Context { + return context.WithValue(ctx, consts.AuthMiddlewareKey, user) +} + +func (a *Service) parseUserJWT(tokenType schema.JWTTokenTypes, jwtToken string) (*user.User, error) { + var sub = consts.AnonymousUser + var jwtIdToken = new(user.User) + + if a.config.Debug.Enabled { + jwtIdToken.Token.Sub = sub + jwtIdToken.Valid = true + return jwtIdToken, nil + } else { + token, err := a.jwks.ParseJWT(jwtToken) + if err != nil { + return nil, consts.ErrNotValidToken + } + + subStr, err := token.Claims.GetSubject() + if err != nil { + return nil, consts.ErrNotValidToken + } + + sub = user.Id(subStr) + + // 如果 token.Header 中没有 typ + if token.Header["typ"] == "" { + return nil, consts.ErrEmptyResponse + } + + // 验证 token 类型 + if tokenType != "" && tokenType.String() != token.Header["typ"] { + return nil, consts.ErrTokenError + } + + jwtIdToken.Valid = true + + err = mapstructure.Decode(token.Claims, &jwtIdToken.Token) + if err != nil { + a.logger.Logger.Error("Failed to map token claims to JwtIDToken struct.\nError: " + err.Error()) + return nil, nil + } + + // 手动指定,因为 mapstructure 无法转换 UserID 类型 + jwtIdToken.Token.Sub = sub + } + + return jwtIdToken, nil +} diff --git a/internal/service/auth/provider.go b/internal/service/auth/provider.go new file mode 100644 index 0000000..6807614 --- /dev/null +++ b/internal/service/auth/provider.go @@ -0,0 +1,25 @@ +package auth + +import ( + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" + "leafdev.top/Leaf/leaf-library-3/internal/base/logger" + "leafdev.top/Leaf/leaf-library-3/internal/service/jwks" +) + +type Service struct { + config *conf.Config + jwks *jwks.JWKS + logger *logger.Logger +} + +func NewService( + config *conf.Config, + jwks *jwks.JWKS, + logger *logger.Logger, +) *Service { + return &Service{ + config: config, + jwks: jwks, + logger: logger, + } +} diff --git a/internal/service/jwks/auth_refresh.go b/internal/service/jwks/auth_refresh.go new file mode 100644 index 0000000..2eb7c3d --- /dev/null +++ b/internal/service/jwks/auth_refresh.go @@ -0,0 +1,23 @@ +package jwks + +import "time" + +var refreshRate = 1 * time.Hour + +func (j *JWKS) SetupAuthRefresh() { + // 先刷新一次 + j.RefreshJWKS() + var firstRefreshed = true + + // 启动一个定时器 + go func() { + for { + if firstRefreshed { + firstRefreshed = false + } else { + j.RefreshJWKS() + } + time.Sleep(refreshRate) + } + }() +} diff --git a/internal/service/jwks/jwks.go b/internal/service/jwks/jwks.go new file mode 100644 index 0000000..07f9ee2 --- /dev/null +++ b/internal/service/jwks/jwks.go @@ -0,0 +1,58 @@ +package jwks + +import ( + "errors" + + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" + "leafdev.top/Leaf/leaf-library-3/internal/base/logger" + + "github.com/MicahParks/keyfunc/v3" + "github.com/golang-jwt/jwt/v5" +) + +var Jwks keyfunc.Keyfunc + +var ( + ErrJWKSNotInitialized = errors.New("JWKS is not initialized") +) + +type JWKS struct { + url string + logger *logger.Logger + config *conf.Config +} + +func NewJWKS(config *conf.Config, logger *logger.Logger) *JWKS { + return &JWKS{ + url: config.JWKS.Url, + logger: logger, + config: config, + } +} + +func (j *JWKS) RefreshJWKS() { + if j.config.Debug.Enabled { + return + } + + j.logger.Logger.Info("Refreshing JWKS...") + + var err error + + Jwks, err = keyfunc.NewDefault([]string{j.url}) + if err != nil { + j.logger.Logger.Error("Failed to create JWK Set from resource at the given URL.\nError: " + err.Error()) + } else { + j.logger.Logger.Info("JWKS refreshed.") + } +} + +func (j *JWKS) ParseJWT(jwtB64 string) (*jwt.Token, error) { + if Jwks.Keyfunc == nil { + return nil, ErrJWKSNotInitialized + } + + token, err := jwt.Parse(jwtB64, Jwks.Keyfunc) + + return token, err +} diff --git a/internal/service/provider.go b/internal/service/provider.go new file mode 100644 index 0000000..eb71efd --- /dev/null +++ b/internal/service/provider.go @@ -0,0 +1,38 @@ +package service + +import ( + "leafdev.top/Leaf/leaf-library-3/internal/base/logger" + "leafdev.top/Leaf/leaf-library-3/internal/service/auth" + "leafdev.top/Leaf/leaf-library-3/internal/service/jwks" + "leafdev.top/Leaf/leaf-library-3/internal/service/stream" + + "github.com/google/wire" +) + +type Service struct { + logger *logger.Logger + Jwks *jwks.JWKS + Auth *auth.Service + Stream *stream.Service +} + +var Provide = wire.NewSet( + jwks.NewJWKS, + auth.NewService, + stream.NewService, + NewService, +) + +func NewService( + logger *logger.Logger, + jwks *jwks.JWKS, + auth *auth.Service, + stream *stream.Service, +) *Service { + return &Service{ + logger, + jwks, + auth, + stream, + } +} diff --git a/internal/service/stream/consumer.go b/internal/service/stream/consumer.go new file mode 100644 index 0000000..f70838a --- /dev/null +++ b/internal/service/stream/consumer.go @@ -0,0 +1,68 @@ +package stream + +import ( + "context" + "fmt" + "github.com/segmentio/kafka-go" + "time" +) + +//func (s *Service) Listen(topic string, handler HandlerFunc) error { +// conn, err := s.dial(topic) +// if err != nil { +// return err +// } +// +// batch := conn.ReadBatch(10e3, 1e6) // fetch 10KB min, 1MB max +// +// b := make([]byte, 10e3) // 10KB max per message +// for { +// n, err := batch.Read(b) +// if err != nil { +// return err +// } +// handler(b[:n]) +// } +//} + +func (s *Service) Consumer(topic string, groupId string) *kafka.Reader { + var r = kafka.ReaderConfig{ + Brokers: s.config.Kafka.BootstrapServers, + GroupID: groupId, + GroupTopics: nil, + Topic: topic, + CommitInterval: time.Second, + //StartOffset: kafka., // 仅对新创建的消费者组生效,从头开始消费,工作中可能更常用从最新的开始消费kafka.LastOffset + Logger: nil, + ErrorLogger: nil, + IsolationLevel: 0, + MaxAttempts: 0, + OffsetOutOfRangeError: false, + MaxBytes: 10e6, // 10MB + } + + if s.config.Kafka.Username != "" && s.config.Kafka.Password != "" { + r.Dialer = &kafka.Dialer{ + Timeout: 10 * time.Second, + DualStack: true, + SASLMechanism: s.auth(), + } + } + + return kafka.NewReader(r) + +} + +type HandlerFunc func([]byte) + +// ReadMessage 消费消息 +func (s *Service) ReadMessage(ctx context.Context, topic string, groupId string) { + for { + if msg, err := s.Consumer(topic, groupId).ReadMessage(ctx); err != nil { + fmt.Println(fmt.Sprintf("读kafka失败,err:%v", err)) + continue + } else { + fmt.Println(fmt.Sprintf("topic=%s,partition=%d,offset=%d,key=%s,value=%s", msg.Topic, msg.Partition, msg.Offset, msg.Key, msg.Value)) + } + } +} diff --git a/internal/service/stream/kafka.go b/internal/service/stream/kafka.go new file mode 100644 index 0000000..60149ee --- /dev/null +++ b/internal/service/stream/kafka.go @@ -0,0 +1,13 @@ +package stream + +import ( + "github.com/segmentio/kafka-go/sasl/plain" +) + +func (s *Service) auth() plain.Mechanism { + mechanism := plain.Mechanism{ + Username: s.config.Kafka.Username, + Password: s.config.Kafka.Password, + } + return mechanism +} diff --git a/internal/service/stream/producer.go b/internal/service/stream/producer.go new file mode 100644 index 0000000..ed5c231 --- /dev/null +++ b/internal/service/stream/producer.go @@ -0,0 +1,120 @@ +package stream + +import ( + "context" + "time" + + "github.com/segmentio/kafka-go" + "leafdev.top/Leaf/leaf-library-3/internal/schema" +) + +//var connections = map[string]*kafka.Conn{} + +//func (s *Service) dial(topic string) (*kafka.Conn, error) { +// +// // 如果topic 存在于 connections 则直接返回 +// if conn, ok := connections[topic]; ok { +// return conn, nil +// } +// +// ctx := context.Background() +// +// conn, err := kafka.DialLeader(ctx, "tcp", s.config.Kafka.BootstrapServers[0], s.topic(topic), 0) +// if err != nil { +// return nil, err +// } +// //err = conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) +// //if err != nil { +// // return conn, err +// //} +// // +// //// set read deadline +// //err = conn.SetReadDeadline(time.Now().Add(10 * time.Second)) +// //if err != nil { +// // return conn, err +// //} +// +// connections[topic] = conn +// +// return conn, nil +//} +// +//func (s *Service) Publish(topic string, message ...[]byte) error { +// conn, err := s.dial(topic) +// if err != nil { +// return err +// } +// +// msg := make([]kafka.Message, len(message)) +// for i, v := range message { +// msg[i] = kafka.Message{Value: v} +// } +// +// _, err = conn.WriteMessages(msg...) +// +// return err +//} + +func (s *Service) Producer(topic string) *kafka.Writer { + var w = &kafka.Writer{ + Addr: kafka.TCP(s.config.Kafka.BootstrapServers...), + Topic: topic, + Balancer: &kafka.Hash{}, // 用于对key进行hash,决定消息发送到哪个分区 + MaxAttempts: 0, + WriteBackoffMin: 0, + WriteBackoffMax: 0, + BatchSize: 0, + BatchBytes: 0, + BatchTimeout: 0, + ReadTimeout: 0, + //WriteTimeout: time.Second, // kafka有时候可能负载很高,写不进去,那么超时后可以放弃写入,用于可以丢消息的场景 + RequiredAcks: kafka.RequireAll, // 不需要任何节点确认就返回 + Async: true, + Completion: nil, + Compression: 0, + Logger: nil, + ErrorLogger: nil, + Transport: nil, + AllowAutoTopicCreation: false, // 第一次发消息的时候,如果topic不存在,就自动创建topic,工作中禁止使用 + } + + if s.config.Kafka.Username != "" && s.config.Kafka.Password != "" { + w.Transport = &kafka.Transport{ + SASL: s.auth(), + } + } + + return w +} + +func (s *Service) SendMessage(ctx context.Context, topic string, data []byte) error { + msg := kafka.Message{ + Partition: 0, + Offset: 0, + HighWaterMark: 0, + //Key: key, + Value: data, + Time: time.Time{}, + } + + err := s.Producer(topic).WriteMessages(ctx, msg) + return err +} + +func (s *Service) SendEvent(ctx context.Context, topic string, data schema.EventMessage) error { + j, err := data.JSON() + if err != nil { + return err + } + + msg := kafka.Message{ + Partition: 0, + Offset: 0, + HighWaterMark: 0, + Value: j, + Time: time.Time{}, + } + + err = s.Producer(topic).WriteMessages(ctx, msg) + return err +} diff --git a/internal/service/stream/provider.go b/internal/service/stream/provider.go new file mode 100644 index 0000000..30ed6bf --- /dev/null +++ b/internal/service/stream/provider.go @@ -0,0 +1,15 @@ +package stream + +import ( + "leafdev.top/Leaf/leaf-library-3/internal/base/conf" +) + +type Service struct { + config *conf.Config +} + +func NewService(config *conf.Config) *Service { + return &Service{ + config, + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..dbe73cc --- /dev/null +++ b/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "leafdev.top/Leaf/leaf-library-3/cmd" +) + +// @title API Docs +// @version 1.0 +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization +func main() { + err := cmd.RootCmd.Execute() + if err != nil { + panic(err) + return + } +} diff --git a/proto/api/v1/document_service.proto b/proto/api/v1/document_service.proto new file mode 100644 index 0000000..aaed224 --- /dev/null +++ b/proto/api/v1/document_service.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package template.document; + +option go_package = "gen/api/v1"; + +service DocumentService { + rpc ListDocuments(ListDocumentsRequest) returns (ListDocumentsResponse); +} + +message ListDocumentsRequest { + string Message = 1; +} + +message ListDocumentsResponse { + string Message = 1; +} \ No newline at end of file diff --git a/proto/gen/apidocs.swagger.json b/proto/gen/apidocs.swagger.json new file mode 100644 index 0000000..c757b1c --- /dev/null +++ b/proto/gen/apidocs.swagger.json @@ -0,0 +1,57 @@ +{ + "swagger": "2.0", + "info": { + "title": "proto/api/v1/document_service.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "DocumentService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "documentListDocumentsResponse": { + "type": "object", + "properties": { + "Message": { + "type": "string" + } + } + }, + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + } + } +} diff --git a/proto/gen/proto/api/v1/document_service.pb.go b/proto/gen/proto/api/v1/document_service.pb.go new file mode 100644 index 0000000..8be0e83 --- /dev/null +++ b/proto/gen/proto/api/v1/document_service.pb.go @@ -0,0 +1,217 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc (unknown) +// source: proto/api/v1/document_service.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ListDocumentsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=Message,proto3" json:"Message,omitempty"` +} + +func (x *ListDocumentsRequest) Reset() { + *x = ListDocumentsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_v1_document_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListDocumentsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListDocumentsRequest) ProtoMessage() {} + +func (x *ListDocumentsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_v1_document_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListDocumentsRequest.ProtoReflect.Descriptor instead. +func (*ListDocumentsRequest) Descriptor() ([]byte, []int) { + return file_proto_api_v1_document_service_proto_rawDescGZIP(), []int{0} +} + +func (x *ListDocumentsRequest) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type ListDocumentsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=Message,proto3" json:"Message,omitempty"` +} + +func (x *ListDocumentsResponse) Reset() { + *x = ListDocumentsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_v1_document_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListDocumentsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListDocumentsResponse) ProtoMessage() {} + +func (x *ListDocumentsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_v1_document_service_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListDocumentsResponse.ProtoReflect.Descriptor instead. +func (*ListDocumentsResponse) Descriptor() ([]byte, []int) { + return file_proto_api_v1_document_service_proto_rawDescGZIP(), []int{1} +} + +func (x *ListDocumentsResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_proto_api_v1_document_service_proto protoreflect.FileDescriptor + +var file_proto_api_v1_document_service_proto_rawDesc = []byte{ + 0x0a, 0x23, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x64, + 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2e, + 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x30, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, + 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x31, 0x0a, 0x15, 0x4c, 0x69, + 0x73, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x75, 0x0a, + 0x0f, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x62, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x12, 0x27, 0x2e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2e, 0x64, 0x6f, 0x63, + 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x74, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x2e, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0c, 0x5a, 0x0a, 0x67, 0x65, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proto_api_v1_document_service_proto_rawDescOnce sync.Once + file_proto_api_v1_document_service_proto_rawDescData = file_proto_api_v1_document_service_proto_rawDesc +) + +func file_proto_api_v1_document_service_proto_rawDescGZIP() []byte { + file_proto_api_v1_document_service_proto_rawDescOnce.Do(func() { + file_proto_api_v1_document_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_api_v1_document_service_proto_rawDescData) + }) + return file_proto_api_v1_document_service_proto_rawDescData +} + +var file_proto_api_v1_document_service_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto_api_v1_document_service_proto_goTypes = []any{ + (*ListDocumentsRequest)(nil), // 0: template.document.ListDocumentsRequest + (*ListDocumentsResponse)(nil), // 1: template.document.ListDocumentsResponse +} +var file_proto_api_v1_document_service_proto_depIdxs = []int32{ + 0, // 0: template.document.DocumentService.ListDocuments:input_type -> template.document.ListDocumentsRequest + 1, // 1: template.document.DocumentService.ListDocuments:output_type -> template.document.ListDocumentsResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] 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_proto_api_v1_document_service_proto_init() } +func file_proto_api_v1_document_service_proto_init() { + if File_proto_api_v1_document_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proto_api_v1_document_service_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*ListDocumentsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_v1_document_service_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*ListDocumentsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_api_v1_document_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_api_v1_document_service_proto_goTypes, + DependencyIndexes: file_proto_api_v1_document_service_proto_depIdxs, + MessageInfos: file_proto_api_v1_document_service_proto_msgTypes, + }.Build() + File_proto_api_v1_document_service_proto = out.File + file_proto_api_v1_document_service_proto_rawDesc = nil + file_proto_api_v1_document_service_proto_goTypes = nil + file_proto_api_v1_document_service_proto_depIdxs = nil +} diff --git a/proto/gen/proto/api/v1/document_service.pb.gw.go b/proto/gen/proto/api/v1/document_service.pb.gw.go new file mode 100644 index 0000000..3f8be4c --- /dev/null +++ b/proto/gen/proto/api/v1/document_service.pb.gw.go @@ -0,0 +1,164 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: proto/api/v1/document_service.proto + +/* +Package v1 is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package v1 + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_DocumentService_ListDocuments_0(ctx context.Context, marshaler runtime.Marshaler, client DocumentServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListDocumentsRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ListDocuments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_DocumentService_ListDocuments_0(ctx context.Context, marshaler runtime.Marshaler, server DocumentServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListDocumentsRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ListDocuments(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterDocumentServiceHandlerServer registers the http handlers for service DocumentService to "mux". +// UnaryRPC :call DocumentServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterDocumentServiceHandlerFromEndpoint instead. +// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. +func RegisterDocumentServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server DocumentServiceServer) error { + + mux.Handle("POST", pattern_DocumentService_ListDocuments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/template.document.DocumentService/ListDocuments", runtime.WithHTTPPathPattern("/template.document.DocumentService/ListDocuments")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_DocumentService_ListDocuments_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_DocumentService_ListDocuments_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterDocumentServiceHandlerFromEndpoint is same as RegisterDocumentServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterDocumentServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.NewClient(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterDocumentServiceHandler(ctx, mux, conn) +} + +// RegisterDocumentServiceHandler registers the http handlers for service DocumentService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterDocumentServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterDocumentServiceHandlerClient(ctx, mux, NewDocumentServiceClient(conn)) +} + +// RegisterDocumentServiceHandlerClient registers the http handlers for service DocumentService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "DocumentServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "DocumentServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "DocumentServiceClient" to call the correct interceptors. This client ignores the HTTP middlewares. +func RegisterDocumentServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client DocumentServiceClient) error { + + mux.Handle("POST", pattern_DocumentService_ListDocuments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/template.document.DocumentService/ListDocuments", runtime.WithHTTPPathPattern("/template.document.DocumentService/ListDocuments")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_DocumentService_ListDocuments_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_DocumentService_ListDocuments_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_DocumentService_ListDocuments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"template.document.DocumentService", "ListDocuments"}, "")) +) + +var ( + forward_DocumentService_ListDocuments_0 = runtime.ForwardResponseMessage +) diff --git a/proto/gen/proto/api/v1/document_service_grpc.pb.go b/proto/gen/proto/api/v1/document_service_grpc.pb.go new file mode 100644 index 0000000..8a92c3b --- /dev/null +++ b/proto/gen/proto/api/v1/document_service_grpc.pb.go @@ -0,0 +1,121 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc (unknown) +// source: proto/api/v1/document_service.proto + +package v1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// 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 ( + DocumentService_ListDocuments_FullMethodName = "/template.document.DocumentService/ListDocuments" +) + +// DocumentServiceClient is the client API for DocumentService 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. +type DocumentServiceClient interface { + ListDocuments(ctx context.Context, in *ListDocumentsRequest, opts ...grpc.CallOption) (*ListDocumentsResponse, error) +} + +type documentServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewDocumentServiceClient(cc grpc.ClientConnInterface) DocumentServiceClient { + return &documentServiceClient{cc} +} + +func (c *documentServiceClient) ListDocuments(ctx context.Context, in *ListDocumentsRequest, opts ...grpc.CallOption) (*ListDocumentsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListDocumentsResponse) + err := c.cc.Invoke(ctx, DocumentService_ListDocuments_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DocumentServiceServer is the server API for DocumentService service. +// All implementations must embed UnimplementedDocumentServiceServer +// for forward compatibility. +type DocumentServiceServer interface { + ListDocuments(context.Context, *ListDocumentsRequest) (*ListDocumentsResponse, error) + mustEmbedUnimplementedDocumentServiceServer() +} + +// UnimplementedDocumentServiceServer 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 UnimplementedDocumentServiceServer struct{} + +func (UnimplementedDocumentServiceServer) ListDocuments(context.Context, *ListDocumentsRequest) (*ListDocumentsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListDocuments not implemented") +} +func (UnimplementedDocumentServiceServer) mustEmbedUnimplementedDocumentServiceServer() {} +func (UnimplementedDocumentServiceServer) testEmbeddedByValue() {} + +// UnsafeDocumentServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DocumentServiceServer will +// result in compilation errors. +type UnsafeDocumentServiceServer interface { + mustEmbedUnimplementedDocumentServiceServer() +} + +func RegisterDocumentServiceServer(s grpc.ServiceRegistrar, srv DocumentServiceServer) { + // If the following call pancis, it indicates UnimplementedDocumentServiceServer 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(&DocumentService_ServiceDesc, srv) +} + +func _DocumentService_ListDocuments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListDocumentsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DocumentServiceServer).ListDocuments(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DocumentService_ListDocuments_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DocumentServiceServer).ListDocuments(ctx, req.(*ListDocumentsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// DocumentService_ServiceDesc is the grpc.ServiceDesc for DocumentService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var DocumentService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "template.document.DocumentService", + HandlerType: (*DocumentServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListDocuments", + Handler: _DocumentService_ListDocuments_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "proto/api/v1/document_service.proto", +}