From 2699d43e47261842996129da3ae850841c540934 Mon Sep 17 00:00:00 2001 From: ivamp Date: Thu, 21 Nov 2024 19:25:32 +0800 Subject: [PATCH] init --- .dockerignore | 2 + .gitea_old/workflows/build.yaml | 97 ++++ .gitea_old/workflows/deploy.yaml | 25 + .gitignore | 4 + Dockerfile | 15 + Dockerfile2 | 8 + Makefile | 15 + README.md | 1 + buf.gen.yaml | 19 + cmd/document.go | 55 ++ cmd/http.go | 100 ++++ cmd/migrate.go | 162 ++++++ cmd/root.go | 14 + cmd/schedule.go | 39 ++ cmd/wire.go | 44 ++ cmd/wire_gen.go | 67 +++ configs/config.go | 6 + configs/config.yaml | 67 +++ docs/docs.go | 128 +++++ docs/swagger.json | 101 ++++ docs/swagger.yaml | 62 +++ go.mod | 119 +++++ go.sum | 499 ++++++++++++++++++ hack/gorm-gen/.gitignore | 1 + hack/gorm-gen/gorm.go | 35 ++ hack/rename/rename.go | 135 +++++ internal/base/app.go | 61 +++ internal/base/conf/conf.go | 104 ++++ internal/base/conf/helper.go | 35 ++ internal/base/conf/provider.go | 71 +++ internal/base/logger/provider.go | 25 + internal/base/milvus/provide.go | 29 + internal/base/orm/provide.go | 43 ++ internal/base/redis/provide.go | 38 ++ internal/base/s3/provide.go | 29 + internal/base/server/echo.go | 99 ++++ internal/batch/provider.go | 16 + internal/dao/gen.go | 103 ++++ internal/dao/provider.go | 7 + internal/dao/users.gen.go | 392 ++++++++++++++ internal/entity/Model.go | 14 + internal/entity/User.go | 10 + internal/handler/grpc/documents/document.go | 11 + internal/handler/grpc/documents/provider.go | 18 + internal/handler/grpc/interceptor/auth.go | 40 ++ .../handler/grpc/interceptor/zap_logger.go | 56 ++ internal/handler/grpc/provider.go | 47 ++ internal/handler/handler.go | 28 + internal/handler/http/controller/v1/test.go | 45 ++ internal/handler/http/middleware/auth.go | 87 +++ .../handler/http/middleware/json_response.go | 22 + .../handler/http/middleware/zap_logger.go | 32 ++ internal/handler/http/provider.go | 50 ++ internal/handler/http/response/http.go | 126 +++++ internal/migrations/1_setup.sql | 15 + internal/migrations/init.go | 11 + internal/router/api.go | 38 ++ internal/router/provider.go | 9 + internal/router/swagger.go | 20 + internal/schema/entity.go | 17 + internal/schema/jwt.go | 12 + internal/schema/stream.go | 22 + internal/schema/user.go | 86 +++ internal/schema/user_response.go | 9 + internal/service/auth/auth.go | 111 ++++ internal/service/jwks/auth_refresh.go | 23 + internal/service/jwks/jwks.go | 57 ++ internal/service/provider.go | 38 ++ internal/service/stream/consumer.go | 68 +++ internal/service/stream/kafka.go | 13 + internal/service/stream/producer.go | 119 +++++ internal/service/stream/provider.go | 15 + main.go | 18 + pkg/consts/auth.go | 31 ++ pkg/consts/model.go | 7 + pkg/consts/rbac.go | 7 + pkg/protos/document/apidocs.swagger.json | 57 ++ .../documentService/apidocs.swagger.json | 52 ++ pkg/protos/documentService/service.pb.go | 214 ++++++++ pkg/protos/documentService/service.pb.gw.go | 164 ++++++ pkg/protos/documentService/service.proto | 15 + pkg/protos/documentService/service_grpc.pb.go | 105 ++++ 82 files changed, 4811 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitea_old/workflows/build.yaml create mode 100644 .gitea_old/workflows/deploy.yaml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Dockerfile2 create mode 100644 Makefile create mode 100644 README.md create mode 100644 buf.gen.yaml create mode 100644 cmd/document.go create mode 100644 cmd/http.go create mode 100644 cmd/migrate.go create mode 100644 cmd/root.go create mode 100644 cmd/schedule.go create mode 100644 cmd/wire.go create mode 100644 cmd/wire_gen.go create mode 100644 configs/config.go create mode 100644 configs/config.yaml create mode 100644 docs/docs.go create mode 100644 docs/swagger.json create mode 100644 docs/swagger.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 hack/gorm-gen/.gitignore create mode 100644 hack/gorm-gen/gorm.go create mode 100644 hack/rename/rename.go create mode 100644 internal/base/app.go create mode 100644 internal/base/conf/conf.go create mode 100644 internal/base/conf/helper.go create mode 100644 internal/base/conf/provider.go create mode 100644 internal/base/logger/provider.go create mode 100644 internal/base/milvus/provide.go create mode 100644 internal/base/orm/provide.go create mode 100644 internal/base/redis/provide.go create mode 100644 internal/base/s3/provide.go create mode 100644 internal/base/server/echo.go create mode 100644 internal/batch/provider.go create mode 100644 internal/dao/gen.go create mode 100644 internal/dao/provider.go create mode 100644 internal/dao/users.gen.go create mode 100644 internal/entity/Model.go create mode 100644 internal/entity/User.go create mode 100644 internal/handler/grpc/documents/document.go create mode 100644 internal/handler/grpc/documents/provider.go create mode 100644 internal/handler/grpc/interceptor/auth.go create mode 100644 internal/handler/grpc/interceptor/zap_logger.go create mode 100644 internal/handler/grpc/provider.go create mode 100644 internal/handler/handler.go create mode 100644 internal/handler/http/controller/v1/test.go create mode 100644 internal/handler/http/middleware/auth.go create mode 100644 internal/handler/http/middleware/json_response.go create mode 100644 internal/handler/http/middleware/zap_logger.go create mode 100644 internal/handler/http/provider.go create mode 100644 internal/handler/http/response/http.go create mode 100644 internal/migrations/1_setup.sql create mode 100644 internal/migrations/init.go create mode 100644 internal/router/api.go create mode 100644 internal/router/provider.go create mode 100644 internal/router/swagger.go create mode 100644 internal/schema/entity.go create mode 100644 internal/schema/jwt.go create mode 100644 internal/schema/stream.go create mode 100644 internal/schema/user.go create mode 100644 internal/schema/user_response.go create mode 100644 internal/service/auth/auth.go create mode 100644 internal/service/jwks/auth_refresh.go create mode 100644 internal/service/jwks/jwks.go create mode 100644 internal/service/provider.go create mode 100644 internal/service/stream/consumer.go create mode 100644 internal/service/stream/kafka.go create mode 100644 internal/service/stream/producer.go create mode 100644 internal/service/stream/provider.go create mode 100644 main.go create mode 100644 pkg/consts/auth.go create mode 100644 pkg/consts/model.go create mode 100644 pkg/consts/rbac.go create mode 100644 pkg/protos/document/apidocs.swagger.json create mode 100644 pkg/protos/documentService/apidocs.swagger.json create mode 100644 pkg/protos/documentService/service.pb.go create mode 100644 pkg/protos/documentService/service.pb.gw.go create mode 100644 pkg/protos/documentService/service.proto create mode 100644 pkg/protos/documentService/service_grpc.pb.go 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..36cb255 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +.PHONY: install-deps generate + +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 + +generate: + go generate ./... + +swag: + swag init -g main.go --parseDependency + +gorm: + cd hack/gorm-gen && go run . \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3dae0ea --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# go-template diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..8e17b29 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,19 @@ +version: v1 + +plugins: + - name: go + out: . + opt: paths=source_relative + - name: go-grpc + out: . + opt: paths=source_relative + - plugin: buf.build/grpc-ecosystem/gateway:v2.22.0 + out: . + opt: + - paths=source_relative + - generate_unbound_methods=true + - plugin: buf.build/grpc-ecosystem/openapiv2:v2.22.0 + out: pkg/protos/document + opt: + - allow_merge +# - merge_file_name=mce-spider \ No newline at end of file diff --git a/cmd/document.go b/cmd/document.go new file mode 100644 index 0000000..d22f2bd --- /dev/null +++ b/cmd/document.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" + "github.com/spf13/cobra" + "go-template/pkg/protos/documentService" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" + "net" +) + +func init() { + RootCmd.AddCommand(documentServiceCommand) +} + +var documentServiceCommand = &cobra.Command{ + Use: "document", + Short: "Start document service", + Run: func(cmd *cobra.Command, args []string) { + app, err := CreateApp() + if err != nil { + panic(err) + return + } + + app.Logger.Sugar.Info("Start document service") + + 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.Handler.GRPC.Interceptor.Logger.ZapLogInterceptor()), + + auth.UnaryServerInterceptor(app.Handler.GRPC.Interceptor.Auth.JwtAuth), + ), + grpc.ChainStreamInterceptor( + logging.StreamServerInterceptor(app.Handler.GRPC.Interceptor.Logger.ZapLogInterceptor()), + auth.StreamServerInterceptor(app.Handler.GRPC.Interceptor.Auth.JwtAuth), + ), + } + grpcServer := grpc.NewServer(opts...) + + documentService.RegisterDocumentServiceServer(grpcServer, app.Handler.GRPC.DocumentService) + reflection.Register(grpcServer) + + app.Logger.Sugar.Info("Document Service listing on " + app.Config.Grpc.Address) + + if err := grpcServer.Serve(lis); err != nil { + app.Logger.Sugar.Fatal(err) + } + }, +} diff --git a/cmd/http.go b/cmd/http.go new file mode 100644 index 0000000..b913445 --- /dev/null +++ b/cmd/http.go @@ -0,0 +1,100 @@ +package cmd + +import ( + "context" + "errors" + "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 = 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 = app.HttpServer.MetricRouter() + + err = metricServer.ListenAndServe() + if err != nil && !errors.Is(http.ErrServerClosed, err) { + panic(err) + return + } + }() + } + + // 等待一个 INT 或 TERM 信号 + quit := make(chan os.Signal) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + app.Logger.Sugar.Info("Shutdown Server ...") + // 创建超时上下文,Shutdown 可以让未处理的连接在这个时间内关闭 + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + // 停止HTTP服务器 + if err := bizServer.Shutdown(ctx); err != nil { + app.Logger.Sugar.Fatal("Biz Server Shutdown Error:", err) + } + + if err := metricServer.Shutdown(ctx); err != nil { + app.Logger.Sugar.Fatal("Metric Server Shutdown Error:", err) + } +} diff --git a/cmd/migrate.go b/cmd/migrate.go new file mode 100644 index 0000000..2773388 --- /dev/null +++ b/cmd/migrate.go @@ -0,0 +1,162 @@ +package cmd + +import ( + "context" + "fmt" + "go-template/internal/migrations" + "log" + "os" + "strings" + "time" + + "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: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + _ = cmd.Help() + return + } + RunMigrate(args) + }, +} + +var createGoMigrateCommand = &cobra.Command{ + Use: "create-migrate", + Short: "新建 go 迁移", + Long: "新建 goose 的 go 迁移。", + 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) + } + + // setup config + migrations.Config = app.Config + + // dic + goose.SetBaseFS(migrations.MigrationFS) + + // dialect tidb + err = goose.SetDialect("tidb") + if err != nil { + return + } + + db, err := app.GORM.DB() + if err != nil { + log.Fatalf("goose: %v\n", 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 { + log.Fatalf("goose: %v\n", err) + } + + defer func() { + if err := db.Close(); err != nil { + log.Fatalf("goose: failed to close DB: %v\n", err) + } + }() + +} + +func createGooseMigration(name string) { + // 在 internal/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/migrations/"+fileName, []byte(template), 0644) + if err != nil { + log.Fatalf("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..01f0589 --- /dev/null +++ b/cmd/schedule.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "go-template/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..4959606 --- /dev/null +++ b/cmd/wire.go @@ -0,0 +1,44 @@ +//go:build wireinject +// +build wireinject + +package cmd + +import ( + "go-template/internal/base" + "go-template/internal/base/conf" + "go-template/internal/base/logger" + "go-template/internal/base/milvus" + "go-template/internal/base/orm" + "go-template/internal/base/redis" + "go-template/internal/base/s3" + "go-template/internal/base/server" + "go-template/internal/batch" + "go-template/internal/dao" + "go-template/internal/handler" + "go-template/internal/router" + "go-template/internal/service" + + "github.com/google/wire" +) + +var ProviderSet = wire.NewSet( + conf.ProviderConfig, + logger.NewZapLogger, + orm.NewGORM, + dao.NewQuery, + redis.NewRedis, + s3.NewS3, + milvus.NewService, + batch.NewBatch, + service.Provider, + handler.ProviderSet, + router.ProviderSetRouter, + 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..81086f8 --- /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" + "go-template/internal/base" + "go-template/internal/base/conf" + "go-template/internal/base/logger" + "go-template/internal/base/milvus" + "go-template/internal/base/orm" + "go-template/internal/base/redis" + "go-template/internal/base/s3" + "go-template/internal/base/server" + "go-template/internal/batch" + "go-template/internal/dao" + "go-template/internal/handler" + "go-template/internal/handler/grpc" + "go-template/internal/handler/grpc/documents" + "go-template/internal/handler/grpc/interceptor" + "go-template/internal/handler/http" + "go-template/internal/handler/http/controller/v1" + "go-template/internal/router" + "go-template/internal/service" + "go-template/internal/service/auth" + "go-template/internal/service/jwks" + "go-template/internal/service/stream" +) + +// Injectors from wire.go: + +func CreateApp() (*base.Application, error) { + loggerLogger := logger.NewZapLogger() + config := conf.ProviderConfig(loggerLogger) + jwksJWKS := jwks.NewJWKS(config, loggerLogger) + authService := auth.NewAuthService(config, jwksJWKS, loggerLogger) + userController := v1.NewUserController(authService) + handlers := http.NewHandler(userController) + api := router.NewApiRoute(handlers) + swaggerRouter := router.NewSwaggerRoute() + middleware := http.NewMiddleware(config, loggerLogger, authService) + httpServer := server.NewHTTPServer(config, api, swaggerRouter, middleware) + db := orm.NewGORM(config, loggerLogger) + query := dao.NewQuery(db) + documentService := documents.NewDocumentService(query) + interceptorAuth := interceptor.NewAuth(authService) + interceptorLogger := interceptor.NewLogger(loggerLogger) + grpcInterceptor := grpc.NewInterceptor(interceptorAuth, interceptorLogger) + grpcHandlers := grpc.NewHandler(documentService, grpcInterceptor) + handlerHandler := handler.NewHandler(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, handlerHandler, loggerLogger, serviceService, middleware, redisRedis, batchBatch, s3S3, db, query, client) + return application, nil +} + +// wire.go: + +var ProviderSet = wire.NewSet(conf.ProviderConfig, logger.NewZapLogger, orm.NewGORM, dao.NewQuery, redis.NewRedis, s3.NewS3, milvus.NewService, batch.NewBatch, service.Provider, handler.ProviderSet, router.ProviderSetRouter, server.NewHTTPServer, base.NewApplication) diff --git a/configs/config.go b/configs/config.go new file mode 100644 index 0000000..bc2ad64 --- /dev/null +++ b/configs/config.go @@ -0,0 +1,6 @@ +package configs + +import _ "embed" + +//go:embed config.yaml +var Config []byte diff --git a/configs/config.yaml b/configs/config.yaml new file mode 100644 index 0000000..655c0d1 --- /dev/null +++ b/configs/config.yaml @@ -0,0 +1,67 @@ +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 + +grpc: + address: 0.0.0.0:9090 + +debug: + enabled: false + +database: + host: 127.0.0.1 + port: 3306 + user: root + password: root + name: db_name + +redis: + host: 127.0.0.1 + port: 6379 + password: "" + db: 0 + +jwks: + url: "" + +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/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..95a3e4b --- /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/schema.ResponseBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.CurrentUserResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/schema.ResponseBody" + } + } + } + } + } + }, + "definitions": { + "schema.CurrentUserResponse": { + "type": "object", + "properties": { + "ip": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userName": { + "type": "string" + }, + "valid": { + "type": "boolean" + } + } + }, + "schema.ResponseBody": { + "type": "object", + "properties": { + "data": {}, + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "success": { + "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..b58d392 --- /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/schema.ResponseBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/schema.CurrentUserResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/schema.ResponseBody" + } + } + } + } + } + }, + "definitions": { + "schema.CurrentUserResponse": { + "type": "object", + "properties": { + "ip": { + "type": "string" + }, + "userEmail": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "userName": { + "type": "string" + }, + "valid": { + "type": "boolean" + } + } + }, + "schema.ResponseBody": { + "type": "object", + "properties": { + "data": {}, + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "success": { + "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..d3d551a --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,62 @@ +definitions: + schema.CurrentUserResponse: + properties: + ip: + type: string + userEmail: + type: string + userId: + type: string + userName: + type: string + valid: + type: boolean + type: object + schema.ResponseBody: + properties: + data: {} + error: + type: string + message: + type: string + success: + 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/schema.ResponseBody' + - properties: + data: + $ref: '#/definitions/schema.CurrentUserResponse' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/schema.ResponseBody' + 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..e22f581 --- /dev/null +++ b/go.mod @@ -0,0 +1,119 @@ +module go-template + +go 1.23.2 + +require ( + github.com/MicahParks/keyfunc/v3 v3.3.5 + github.com/bsm/redislock v0.9.4 + 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/labstack/echo-contrib v0.17.1 + github.com/labstack/echo/v4 v4.12.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/echo-swagger v1.4.1 + 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/mysql v1.5.7 + 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/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.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/ghodss/yaml v1.0.0 // 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-jwt/jwt v3.2.2+incompatible // 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/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/labstack/gommon v0.4.2 // 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-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/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/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + go.uber.org/multierr v1.11.0 // 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.27.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.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/datatypes v1.2.4 // indirect + gorm.io/hints v1.1.2 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5f5cb92 --- /dev/null +++ b/go.sum @@ -0,0 +1,499 @@ +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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +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/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +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/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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +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/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/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +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/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +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/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +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/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +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 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +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/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/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/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +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/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.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +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/labstack/echo-contrib v0.17.1 h1:7I/he7ylVKsDUieaGRZ9XxxTYOjfQwVzHzUYrNykfCU= +github.com/labstack/echo-contrib v0.17.1/go.mod h1:SnsCZtwHBAZm5uBSAtQtXQHI3wqEA73hvTn0bYMKnZA= +github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= +github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +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-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.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/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +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/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +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/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/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +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/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/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +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/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +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/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +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/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.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/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk= +github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc= +github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= +github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= +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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +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/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.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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +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-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.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.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +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/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.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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +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.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= +gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A= +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.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= +gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= +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/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/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.33.0 h1:WWkA/T2G17okiLGgKAj4/RMIvgyMT19yQ038160IeYk= +modernc.org/sqlite v1.33.0/go.mod h1:9uQ9hF/pCZoYZK73D/ud5Z7cIRIILSZI8NdIemVMTX8= +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= 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..18498b0 --- /dev/null +++ b/hack/gorm-gen/gorm.go @@ -0,0 +1,35 @@ +package main + +import ( + "go-template/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..9cf213f --- /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/ecosystem/billing" + +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/base/app.go b/internal/base/app.go new file mode 100644 index 0000000..8ff677f --- /dev/null +++ b/internal/base/app.go @@ -0,0 +1,61 @@ +package base + +import ( + "github.com/milvus-io/milvus-sdk-go/v2/client" + "go-template/internal/base/conf" + "go-template/internal/base/logger" + "go-template/internal/base/redis" + "go-template/internal/base/s3" + "go-template/internal/base/server" + "go-template/internal/batch" + "go-template/internal/dao" + "go-template/internal/handler" + "go-template/internal/handler/http" + "go-template/internal/service" + "gorm.io/gorm" +) + +type Application struct { + Config *conf.Config + Logger *logger.Logger + Handler *handler.Handler + Middleware *http.Middleware + 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, + handler *handler.Handler, + logger *logger.Logger, + services *service.Service, + middleware *http.Middleware, + 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, + Handler: handler, + Logger: logger, + Service: services, + Middleware: middleware, + 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..80f1cdf --- /dev/null +++ b/internal/base/conf/conf.go @@ -0,0 +1,104 @@ +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"` + Port int `yaml:"port"` + Url string `yaml:"url"` +} + +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"` +} + +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"` +} diff --git a/internal/base/conf/helper.go b/internal/base/conf/helper.go new file mode 100644 index 0000000..877d87e --- /dev/null +++ b/internal/base/conf/helper.go @@ -0,0 +1,35 @@ +package conf + +import ( + "go-template/configs" + "os" +) + +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..2874511 --- /dev/null +++ b/internal/base/conf/provider.go @@ -0,0 +1,71 @@ +package conf + +import ( + "go-template/internal/base/logger" + "os" + "path/filepath" + "strings" + + "github.com/spf13/viper" +) + +// depth 是配置文件的搜索深度 +var depth = 8 + +func getConfigPath() string { + var path string + if os.Getenv("AMBER_CONFIG") != "" { + path = os.Getenv("AMBER_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 ProviderConfig(logger *logger.Logger) *Config { + var path = getConfigPath() + createConfigIfNotExists(path) + + if path == "" { + logger.Sugar.Fatal("config file not found, created.") + } else { + logger.Sugar.Infof("config file found at %s", path) + } + + c := &Config{} + v := viper.New() + v.SetConfigType("yaml") + v.SetConfigFile(path) + if err := v.ReadInConfig(); err != nil { + logger.Sugar.Fatal(err) + } + if err := v.Unmarshal(c); err != nil { + panic(err) + } + + return c +} diff --git a/internal/base/logger/provider.go b/internal/base/logger/provider.go new file mode 100644 index 0000000..06842ba --- /dev/null +++ b/internal/base/logger/provider.go @@ -0,0 +1,25 @@ +package logger + +import "go.uber.org/zap" + +type Logger struct { + Sugar *zap.SugaredLogger + Logger *zap.Logger +} + +func NewZapLogger() *Logger { + 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..3c96f3b --- /dev/null +++ b/internal/base/milvus/provide.go @@ -0,0 +1,29 @@ +package milvus + +import ( + "context" + "github.com/milvus-io/milvus-sdk-go/v2/client" + "go-template/internal/base/conf" + "go-template/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..75e0410 --- /dev/null +++ b/internal/base/orm/provide.go @@ -0,0 +1,43 @@ +package orm + +import ( + "fmt" + "go-template/internal/base/conf" + "go-template/internal/base/logger" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + "moul.io/zapgorm2" +) + +func NewGORM( + config *conf.Config, + logger *logger.Logger, +) *gorm.DB { + dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", config.Database.User, config.Database.Password, config.Database.Host, config.Database.Port, config.Database.Name) + + var d = mysql.Open(dsn) + + if config.Debug.Enabled { + db, err := gorm.Open(d) + + if err != nil { + panic(err) + } + + return db + } + + zapGormLogger := zapgorm2.New(logger.Logger) + zapGormLogger.SetAsDefault() + db, err := gorm.Open(d, &gorm.Config{ + Logger: zapGormLogger, + }) + + 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..516f5bd --- /dev/null +++ b/internal/base/redis/provide.go @@ -0,0 +1,38 @@ +package redis + +import ( + "context" + "fmt" + "go-template/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..91a5a8b --- /dev/null +++ b/internal/base/s3/provide.go @@ -0,0 +1,29 @@ +package s3 + +import ( + "go-template/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/echo.go b/internal/base/server/echo.go new file mode 100644 index 0000000..086844c --- /dev/null +++ b/internal/base/server/echo.go @@ -0,0 +1,99 @@ +package server + +import ( + "github.com/labstack/echo-contrib/echoprometheus" + "github.com/labstack/echo/v4" + echoMiddleware "github.com/labstack/echo/v4/middleware" + "go-template/internal/base/conf" + httpHandlers "go-template/internal/handler/http" + "go-template/internal/handler/http/response" + "go-template/internal/router" + "go-template/pkg/consts" + "net/http" +) + +type HttpServer struct { + config *conf.Config + Echo *echo.Echo + apiRouter *router.Api + swaggerRouter *router.SwaggerRouter + middleware *httpHandlers.Middleware +} + +// NewHTTPServer new http server. +func NewHTTPServer( + config *conf.Config, + apiRouter *router.Api, + swaggerRouter *router.SwaggerRouter, + middleware *httpHandlers.Middleware, +) *HttpServer { + + e := echo.New() + e.Use(echoMiddleware.Recover()) + e.Use(middleware.Logger.Handler()) + e.Use(middleware.JSONResponse.Handler()) + + return &HttpServer{ + config: config, + Echo: e, + apiRouter: apiRouter, + swaggerRouter: swaggerRouter, + middleware: middleware, + } +} + +func (hs *HttpServer) AllowAllCors() { + var defaultCORSConfig = echoMiddleware.CORSConfig{ + AllowOrigins: []string{"*"}, + AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "X-Requested-With", "X-Auth-Token", "Authorization"}, + AllowCredentials: true, + AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete}, + MaxAge: 12 * 60, + } + + hs.Echo.Use(echoMiddleware.CORSWithConfig(defaultCORSConfig)) +} + +func (hs *HttpServer) BizRouter() *echo.Echo { + hs.AllowAllCors() + + rootGroup := hs.Echo.Group("") + + // swagger + hs.swaggerRouter.Register(rootGroup) + + apiV1 := rootGroup.Group("/api/v1") + { + //apiV1.Use(corsMiddleWare) + apiV1.Use(hs.middleware.JSONResponse.Handler()) + apiV1.Use(hs.middleware.Auth.Handler()) + hs.apiRouter.InitApiRouter(apiV1) + } + + apiV1NoAuth := rootGroup.Group("/api/v1") + { + //apiV1.Use(corsMiddleWare) + hs.apiRouter.InitNoAuthApiRouter(apiV1NoAuth) + } + + hs.Echo.RouteNotFound("/*", func(ctx echo.Context) error { + return response.Ctx(ctx).Status(http.StatusNotFound).Error(consts.ErrPageNotFound).Send() + }) + + return hs.Echo +} + +func (hs *HttpServer) MetricRouter() *echo.Echo { + e := echo.New() + e.Use(echoMiddleware.Recover()) + + metricGroup := e.Group("") + // prometheus + metricGroup.Use(echoprometheus.NewMiddleware(hs.config.App.Name)) + + metricGroup.GET("/metrics", echoprometheus.NewHandler()) + + metricGroup.GET("/healthz", func(ctx echo.Context) error { return ctx.String(200, "OK") }) + + return e +} diff --git a/internal/batch/provider.go b/internal/batch/provider.go new file mode 100644 index 0000000..4525cb5 --- /dev/null +++ b/internal/batch/provider.go @@ -0,0 +1,16 @@ +package batch + +import ( + "go-template/internal/base/logger" +) + +type Batch struct { + logger *logger.Logger +} + +func NewBatch( + logger *logger.Logger, +) *Batch { + //base.NewApplication() + return &Batch{logger} +} 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..342b46c --- /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" + + "go-template/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/entity/Model.go b/internal/entity/Model.go new file mode 100644 index 0000000..aa89f82 --- /dev/null +++ b/internal/entity/Model.go @@ -0,0 +1,14 @@ +package entity + +import ( + "go-template/internal/schema" + "time" +) + +// 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..c7c65ae --- /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 "user" +} diff --git a/internal/handler/grpc/documents/document.go b/internal/handler/grpc/documents/document.go new file mode 100644 index 0000000..ddf32c7 --- /dev/null +++ b/internal/handler/grpc/documents/document.go @@ -0,0 +1,11 @@ +package documents + +import ( + "context" + "go-template/pkg/protos/documentService" +) + +func (d *DocumentService) ListDocuments(ctx context.Context, req *documentService.ListDocumentsRequest) (*documentService.ListDocumentsResponse, error) { + + return &documentService.ListDocumentsResponse{}, nil +} diff --git a/internal/handler/grpc/documents/provider.go b/internal/handler/grpc/documents/provider.go new file mode 100644 index 0000000..3b020b6 --- /dev/null +++ b/internal/handler/grpc/documents/provider.go @@ -0,0 +1,18 @@ +package documents + +import ( + "go-template/internal/dao" + "go-template/pkg/protos/documentService" +) + +type DocumentService struct { + documentService.UnimplementedDocumentServiceServer + dao *dao.Query +} + +func NewDocumentService(dao *dao.Query) *DocumentService { + return &DocumentService{ + documentService.UnimplementedDocumentServiceServer{}, + dao, + } +} diff --git a/internal/handler/grpc/interceptor/auth.go b/internal/handler/grpc/interceptor/auth.go new file mode 100644 index 0000000..bb3d407 --- /dev/null +++ b/internal/handler/grpc/interceptor/auth.go @@ -0,0 +1,40 @@ +package interceptor + +import ( + "context" + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" + "go-template/internal/schema" + auth2 "go-template/internal/service/auth" + "go-template/pkg/consts" +) + +type Auth struct { + authService *auth2.Service +} + +func NewAuth(authService *auth2.Service) *Auth { + return &Auth{ + authService: authService, + } +} + +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{"auth.sub", token.Token.Sub}) + + return a.authService.SetUser(ctx, token), nil +} diff --git a/internal/handler/grpc/interceptor/zap_logger.go b/internal/handler/grpc/interceptor/zap_logger.go new file mode 100644 index 0000000..93add19 --- /dev/null +++ b/internal/handler/grpc/interceptor/zap_logger.go @@ -0,0 +1,56 @@ +package interceptor + +import ( + "context" + "fmt" + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" + "go-template/internal/base/logger" + "go.uber.org/zap" +) + +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/handler/grpc/provider.go b/internal/handler/grpc/provider.go new file mode 100644 index 0000000..ddd1b6a --- /dev/null +++ b/internal/handler/grpc/provider.go @@ -0,0 +1,47 @@ +package grpc + +import ( + "github.com/google/wire" + "go-template/internal/handler/grpc/documents" + "go-template/internal/handler/grpc/interceptor" +) + +var ProviderSet = wire.NewSet( + interceptor.NewAuth, + interceptor.NewLogger, + documents.NewDocumentService, + + NewInterceptor, + NewHandler, +) + +func NewHandler( + documentService *documents.DocumentService, + interceptor2 *Interceptor, +) *Handlers { + return &Handlers{ + DocumentService: documentService, + Interceptor: interceptor2, + } +} + +type Handlers struct { + DocumentService *documents.DocumentService + 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/handler/handler.go b/internal/handler/handler.go new file mode 100644 index 0000000..a87250a --- /dev/null +++ b/internal/handler/handler.go @@ -0,0 +1,28 @@ +package handler + +import ( + "github.com/google/wire" + "go-template/internal/handler/grpc" + "go-template/internal/handler/http" +) + +var ProviderSet = wire.NewSet( + grpc.ProviderSet, + http.ProviderSet, + NewHandler, +) + +type Handler struct { + GRPC *grpc.Handlers + HTTP *http.Handlers +} + +func NewHandler( + grpcHandlers *grpc.Handlers, + httpHandlers *http.Handlers, +) *Handler { + return &Handler{ + GRPC: grpcHandlers, + HTTP: httpHandlers, + } +} diff --git a/internal/handler/http/controller/v1/test.go b/internal/handler/http/controller/v1/test.go new file mode 100644 index 0000000..3648bad --- /dev/null +++ b/internal/handler/http/controller/v1/test.go @@ -0,0 +1,45 @@ +package v1 + +import ( + "github.com/labstack/echo/v4" + "go-template/internal/handler/http/response" + "go-template/internal/schema" + "go-template/internal/service/auth" + "net/http" +) + +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} schema.ResponseBody{data=schema.CurrentUserResponse} +// @Failure 400 {object} schema.ResponseBody +// @Router /api/v1/ping [get] +func (u *UserController) Test(c echo.Context) error { + user, ok := u.authService.GetUser(c) + if !ok { + return response.Ctx(c).Status(http.StatusUnauthorized).Send() + } + + var currentUserResponse = &schema.CurrentUserResponse{ + IP: c.Request().RemoteAddr, + Valid: user.Valid, + UserEmail: user.Token.Email, + UserId: user.Token.Sub, + UserName: user.Token.Name, + } + + return response.Ctx(c).Data(currentUserResponse).Send() +} diff --git a/internal/handler/http/middleware/auth.go b/internal/handler/http/middleware/auth.go new file mode 100644 index 0000000..10a63be --- /dev/null +++ b/internal/handler/http/middleware/auth.go @@ -0,0 +1,87 @@ +package middleware + +import ( + "github.com/labstack/echo/v4" + "go-template/internal/base/conf" + "go-template/internal/handler/http/response" + "go-template/internal/schema" + "go-template/internal/service/auth" + "go-template/pkg/consts" + "net/http" + "slices" + "strings" +) + +type AuthMiddleware struct { + config *conf.Config + authService *auth.Service +} + +var audienceLength int + +func NewAuthMiddleware(config *conf.Config, authService *auth.Service) *AuthMiddleware { + audienceLength = len(config.App.AllowedAudiences) + + return &AuthMiddleware{ + config, + authService, + } +} + +func (a *AuthMiddleware) Handler() echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + var r = response.Ctx(c) + var err error + var token *schema.User + + if a.config.Debug.Enabled { + token, err = a.authService.AuthFromToken(schema.JWTAccessToken, "") + if err != nil { + return r.Error(err).Send() + } + + c.Set(consts.AuthMiddlewareKey, token) + + return next(c) + } + + authorization := c.Request().Header.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.Set(consts.AuthMiddlewareKey, token) + + return next(c) + } + } + +} diff --git a/internal/handler/http/middleware/json_response.go b/internal/handler/http/middleware/json_response.go new file mode 100644 index 0000000..4125daa --- /dev/null +++ b/internal/handler/http/middleware/json_response.go @@ -0,0 +1,22 @@ +package middleware + +import ( + "github.com/labstack/echo/v4" +) + +type JSONResponseMiddleware struct { +} + +func (*JSONResponseMiddleware) Handler() echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + c.Response().Header().Set("Content-Type", "application/json") + + return next(c) + } + } +} + +func NewJSONResponseMiddleware() *JSONResponseMiddleware { + return &JSONResponseMiddleware{} +} diff --git a/internal/handler/http/middleware/zap_logger.go b/internal/handler/http/middleware/zap_logger.go new file mode 100644 index 0000000..c60d6dd --- /dev/null +++ b/internal/handler/http/middleware/zap_logger.go @@ -0,0 +1,32 @@ +package middleware + +import ( + "github.com/labstack/echo/v4" + echoMiddleware "github.com/labstack/echo/v4/middleware" + "go.uber.org/zap" +) + +type LoggerMiddleware struct { + logger *zap.Logger +} + +func NewLoggerMiddleware(logger *zap.Logger) *LoggerMiddleware { + return &LoggerMiddleware{ + logger: logger, + } +} + +func (l *LoggerMiddleware) Handler() echo.MiddlewareFunc { + return echoMiddleware.RequestLoggerWithConfig(echoMiddleware.RequestLoggerConfig{ + LogURI: true, + LogStatus: true, + LogValuesFunc: func(c echo.Context, v echoMiddleware.RequestLoggerValues) error { + l.logger.Info("request", + zap.String("URI", v.URI), + zap.Int("status", v.Status), + ) + + return nil + }, + }) +} diff --git a/internal/handler/http/provider.go b/internal/handler/http/provider.go new file mode 100644 index 0000000..f625793 --- /dev/null +++ b/internal/handler/http/provider.go @@ -0,0 +1,50 @@ +package http + +import ( + "github.com/google/wire" + "github.com/labstack/echo/v4" + "go-template/internal/base/conf" + "go-template/internal/base/logger" + v1 "go-template/internal/handler/http/controller/v1" + "go-template/internal/handler/http/middleware" + "go-template/internal/service/auth" +) + +type IMiddleware interface { + Handler() echo.MiddlewareFunc +} + +type Middleware struct { + Logger IMiddleware + Auth IMiddleware + JSONResponse IMiddleware +} + +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.NewLoggerMiddleware(logger.Logger), + Auth: middleware.NewAuthMiddleware(config, authService), + JSONResponse: middleware.NewJSONResponseMiddleware(), + } +} + +var ProviderSet = wire.NewSet( + middleware.NewAuthMiddleware, + middleware.NewLoggerMiddleware, + middleware.NewJSONResponseMiddleware, + NewMiddleware, + v1.NewUserController, + NewHandler, +) diff --git a/internal/handler/http/response/http.go b/internal/handler/http/response/http.go new file mode 100644 index 0000000..dd9c26f --- /dev/null +++ b/internal/handler/http/response/http.go @@ -0,0 +1,126 @@ +package response + +import ( + "github.com/labstack/echo/v4" + "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 echo.Context +} + +func Ctx(c echo.Context) *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 r.body.Wrap { + return r.ctx.JSON(r.httpStatus, r.body) + } + + return r.ctx.JSON(r.httpStatus, 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/migrations/1_setup.sql b/internal/migrations/1_setup.sql new file mode 100644 index 0000000..d942961 --- /dev/null +++ b/internal/migrations/1_setup.sql @@ -0,0 +1,15 @@ +-- +goose Up + +CREATE TABLE IF NOT EXISTS `users` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `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, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- +goose Down + +DROP TABLE `users`; \ No newline at end of file diff --git a/internal/migrations/init.go b/internal/migrations/init.go new file mode 100644 index 0000000..1a0d72e --- /dev/null +++ b/internal/migrations/init.go @@ -0,0 +1,11 @@ +package migrations + +import ( + "embed" + "go-template/internal/base/conf" +) + +//go:embed *.sql +var MigrationFS embed.FS + +var Config *conf.Config diff --git a/internal/router/api.go b/internal/router/api.go new file mode 100644 index 0000000..6bcbf65 --- /dev/null +++ b/internal/router/api.go @@ -0,0 +1,38 @@ +package router + +import ( + "github.com/labstack/echo/v4" + "go-template/internal/handler/http" +) + +// 两种方法都可以 +//type Api struct { +// User *v1.UserController +//} + +type Api struct { + HttpHandler *http.Handlers +} + +func NewApiRoute( + //User *v1.UserController, + HttpHandler *http.Handlers, +) *Api { + //return &Api{ + // User, + //} + + return &Api{ + HttpHandler, + } +} + +func (a *Api) InitApiRouter(r *echo.Group) { + //r.GET("/ping", a.User.Test) + + r.GET("/ping", a.HttpHandler.User.Test) +} + +func (a *Api) InitNoAuthApiRouter(r *echo.Group) { + +} diff --git a/internal/router/provider.go b/internal/router/provider.go new file mode 100644 index 0000000..cce6bce --- /dev/null +++ b/internal/router/provider.go @@ -0,0 +1,9 @@ +package router + +import "github.com/google/wire" + +// ProviderSetRouter is providers. +var ProviderSetRouter = wire.NewSet( + NewApiRoute, + NewSwaggerRoute, +) diff --git a/internal/router/swagger.go b/internal/router/swagger.go new file mode 100644 index 0000000..3c54de1 --- /dev/null +++ b/internal/router/swagger.go @@ -0,0 +1,20 @@ +package router + +import ( + "github.com/labstack/echo/v4" + _ "go-template/docs" + + echoSwagger "github.com/swaggo/echo-swagger" +) + +type SwaggerRouter struct { + //config *conf.Config +} + +func NewSwaggerRoute() *SwaggerRouter { + return &SwaggerRouter{} +} + +func (a *SwaggerRouter) Register(e *echo.Group) { + e.GET("/swagger/*", echoSwagger.WrapHandler) +} diff --git a/internal/schema/entity.go b/internal/schema/entity.go new file mode 100644 index 0000000..5fed106 --- /dev/null +++ b/internal/schema/entity.go @@ -0,0 +1,17 @@ +package schema + +import ( + "strconv" +) + +type EntityId uint + +//type EntityId int64 + +func (i EntityId) String() string { + return strconv.FormatUint(uint64(i), 10) + //return strconv.FormatInt(int64(i), 10) +} +func (i EntityId) Uint() uint { + return uint(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.go b/internal/schema/user.go new file mode 100644 index 0000000..d343dff --- /dev/null +++ b/internal/schema/user.go @@ -0,0 +1,86 @@ +package schema + +import ( + "time" +) + +type UserTokenInfo struct { + Aud string `json:"aud"` + Iss string `json:"iss"` + Iat float64 `json:"iat"` + Exp float64 `json:"exp"` + Sub UserId `json:"sub" mapstructure:"-"` + Scopes []string `json:"scopes"` + Roles *UserRoles `json:"roles"` + Permissions *UserPermissions `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 UserTokenInfo + Valid bool +} + +type UserRole string +type UserPermission struct{} + +type UserRoles []UserRole +type UserPermissions []UserPermission + +type UserId string + +func (u *User) GetId() UserId { + 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() *UserRoles { + return u.Token.Roles +} + +func (u *User) GetPermissions() *UserPermissions { + return u.Token.Permissions +} + +func (u UserId) String() string { + return string(u) +} + +func (ur *UserRoles) Has(role UserRole) bool { + for _, r := range *ur { + if r == role { + return true + } + } + + return false +} + +func (up *UserPermissions) Has(permission UserPermission) bool { + for _, p := range *up { + if p == permission { + return true + } + } + + return false +} diff --git a/internal/schema/user_response.go b/internal/schema/user_response.go new file mode 100644 index 0000000..8f5a646 --- /dev/null +++ b/internal/schema/user_response.go @@ -0,0 +1,9 @@ +package schema + +type CurrentUserResponse struct { + IP string `json:"ip"` + Valid bool `json:"valid"` + UserEmail string `json:"userEmail"` + UserId UserId `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..947769e --- /dev/null +++ b/internal/service/auth/auth.go @@ -0,0 +1,111 @@ +package auth + +import ( + "context" + "github.com/labstack/echo/v4" + "github.com/mitchellh/mapstructure" + "go-template/internal/base/conf" + "go-template/internal/base/logger" + "go-template/internal/schema" + "go-template/internal/service/jwks" + "go-template/pkg/consts" +) + +type Service struct { + config *conf.Config + jwks *jwks.JWKS + logger *logger.Logger +} + +func NewAuthService(config *conf.Config, jwks *jwks.JWKS, logger *logger.Logger) *Service { + return &Service{ + config: config, + jwks: jwks, + logger: logger, + } +} + +func (a *Service) AuthFromToken(tokenType schema.JWTTokenTypes, token string) (*schema.User, error) { + if a.config.Debug.Enabled { + return a.parseUserJWT(tokenType, "") + } + + return a.parseUserJWT(tokenType, token) +} + +func (a *Service) GetUserFromIdToken(idToken string) (*schema.User, error) { + return a.parseUserJWT(schema.JWTIDToken, idToken) +} + +func (a *Service) GetUserId(ctx echo.Context) (schema.UserId, error) { + user, ok := a.GetUser(ctx) + + if !ok { + return "", consts.ErrUnauthorized + } + + return user.Token.Sub, nil +} + +func (a *Service) GetUser(ctx echo.Context) (*schema.User, bool) { + user := ctx.Get(consts.AuthMiddlewareKey) + + u, ok := user.(*schema.User) + return u, ok +} + +func (a *Service) SetUser(ctx context.Context, user *schema.User) context.Context { + context.WithValue(ctx, consts.AuthMiddlewareKey, user) + return context.WithValue(ctx, consts.AuthMiddlewareKey, user) +} + +func (a *Service) parseUserJWT(tokenType schema.JWTTokenTypes, jwtToken string) (*schema.User, error) { + var sub = consts.AnonymousUser + var jwtIdToken = &schema.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 + } + + //subInt, err := strconv.Atoi(subStr) + //if err != nil { + // return nil, consts.ErrNotValidToken + //} + + sub = schema.UserId(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/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..31e361e --- /dev/null +++ b/internal/service/jwks/jwks.go @@ -0,0 +1,57 @@ +package jwks + +import ( + "errors" + "go-template/internal/base/conf" + "go-template/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()) + } + + 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..4a9e225 --- /dev/null +++ b/internal/service/provider.go @@ -0,0 +1,38 @@ +package service + +import ( + "go-template/internal/base/logger" + "go-template/internal/service/auth" + "go-template/internal/service/jwks" + "go-template/internal/service/stream" + + "github.com/google/wire" +) + +type Service struct { + logger *logger.Logger + Jwks *jwks.JWKS + Auth *auth.Service + Stream *stream.Service +} + +var Provider = wire.NewSet( + jwks.NewJWKS, + auth.NewAuthService, + 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..e5ca44f --- /dev/null +++ b/internal/service/stream/producer.go @@ -0,0 +1,119 @@ +package stream + +import ( + "context" + "github.com/segmentio/kafka-go" + "go-template/internal/schema" + "time" +) + +//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..37ece3f --- /dev/null +++ b/internal/service/stream/provider.go @@ -0,0 +1,15 @@ +package stream + +import ( + "go-template/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..757ee12 --- /dev/null +++ b/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "go-template/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/pkg/consts/auth.go b/pkg/consts/auth.go new file mode 100644 index 0000000..37bce18 --- /dev/null +++ b/pkg/consts/auth.go @@ -0,0 +1,31 @@ +package consts + +import ( + "errors" + "go-template/internal/schema" +) + +const ( + AuthHeader = "Authorization" + AuthPrefix = "Bearer" + + //AnonymousUser schema.UserId = 1 + AnonymousUser schema.UserId = "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") + ErrBearerToken = errors.New("无效的 Bearer 令牌") + + ErrNotYourResource = errors.New("this resource not yours") + ErrPermissionDenied = errors.New("permission denied") +) diff --git a/pkg/consts/model.go b/pkg/consts/model.go new file mode 100644 index 0000000..58eeb88 --- /dev/null +++ b/pkg/consts/model.go @@ -0,0 +1,7 @@ +package consts + +import "errors" + +var ( + ErrPageNotFound = errors.New("page not found") +) diff --git a/pkg/consts/rbac.go b/pkg/consts/rbac.go new file mode 100644 index 0000000..259daae --- /dev/null +++ b/pkg/consts/rbac.go @@ -0,0 +1,7 @@ +package consts + +import "go-template/internal/schema" + +const ( + RoleSuperAdmin schema.UserRole = "super-admin" +) diff --git a/pkg/protos/document/apidocs.swagger.json b/pkg/protos/document/apidocs.swagger.json new file mode 100644 index 0000000..df08354 --- /dev/null +++ b/pkg/protos/document/apidocs.swagger.json @@ -0,0 +1,57 @@ +{ + "swagger": "2.0", + "info": { + "title": "pkg/protos/documentService/service.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "DocumentService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "ListDocumentsResponse": { + "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/pkg/protos/documentService/apidocs.swagger.json b/pkg/protos/documentService/apidocs.swagger.json new file mode 100644 index 0000000..190e874 --- /dev/null +++ b/pkg/protos/documentService/apidocs.swagger.json @@ -0,0 +1,52 @@ +{ + "swagger": "2.0", + "info": { + "title": "pkg/protos/document/service.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "DocumentService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "ListDocumentsResponse": { + "type": "object" + }, + "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/pkg/protos/documentService/service.pb.go b/pkg/protos/documentService/service.pb.go new file mode 100644 index 0000000..e599ffc --- /dev/null +++ b/pkg/protos/documentService/service.pb.go @@ -0,0 +1,214 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc (unknown) +// source: pkg/protos/documentService/service.proto + +package documentService + +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_pkg_protos_documentService_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_pkg_protos_documentService_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_pkg_protos_documentService_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_pkg_protos_documentService_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_pkg_protos_documentService_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_pkg_protos_documentService_service_proto_rawDescGZIP(), []int{1} +} + +func (x *ListDocumentsResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_pkg_protos_documentService_service_proto protoreflect.FileDescriptor + +var file_pkg_protos_documentService_service_proto_rawDesc = []byte{ + 0x0a, 0x28, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x64, 0x6f, 0x63, + 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 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, + 0x51, 0x0a, 0x0f, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x12, 0x15, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x42, 0x13, 0x5a, 0x11, 0x2e, 0x3b, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_pkg_protos_documentService_service_proto_rawDescOnce sync.Once + file_pkg_protos_documentService_service_proto_rawDescData = file_pkg_protos_documentService_service_proto_rawDesc +) + +func file_pkg_protos_documentService_service_proto_rawDescGZIP() []byte { + file_pkg_protos_documentService_service_proto_rawDescOnce.Do(func() { + file_pkg_protos_documentService_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_pkg_protos_documentService_service_proto_rawDescData) + }) + return file_pkg_protos_documentService_service_proto_rawDescData +} + +var file_pkg_protos_documentService_service_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_pkg_protos_documentService_service_proto_goTypes = []interface{}{ + (*ListDocumentsRequest)(nil), // 0: ListDocumentsRequest + (*ListDocumentsResponse)(nil), // 1: ListDocumentsResponse +} +var file_pkg_protos_documentService_service_proto_depIdxs = []int32{ + 0, // 0: DocumentService.ListDocuments:input_type -> ListDocumentsRequest + 1, // 1: DocumentService.ListDocuments:output_type -> 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_pkg_protos_documentService_service_proto_init() } +func file_pkg_protos_documentService_service_proto_init() { + if File_pkg_protos_documentService_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_pkg_protos_documentService_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListDocumentsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_protos_documentService_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + 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_pkg_protos_documentService_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_pkg_protos_documentService_service_proto_goTypes, + DependencyIndexes: file_pkg_protos_documentService_service_proto_depIdxs, + MessageInfos: file_pkg_protos_documentService_service_proto_msgTypes, + }.Build() + File_pkg_protos_documentService_service_proto = out.File + file_pkg_protos_documentService_service_proto_rawDesc = nil + file_pkg_protos_documentService_service_proto_goTypes = nil + file_pkg_protos_documentService_service_proto_depIdxs = nil +} diff --git a/pkg/protos/documentService/service.pb.gw.go b/pkg/protos/documentService/service.pb.gw.go new file mode 100644 index 0000000..d55bfe8 --- /dev/null +++ b/pkg/protos/documentService/service.pb.gw.go @@ -0,0 +1,164 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: pkg/protos/documentService/service.proto + +/* +Package documentService is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package documentService + +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, "/.DocumentService/ListDocuments", runtime.WithHTTPPathPattern("/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, "/.DocumentService/ListDocuments", runtime.WithHTTPPathPattern("/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{"DocumentService", "ListDocuments"}, "")) +) + +var ( + forward_DocumentService_ListDocuments_0 = runtime.ForwardResponseMessage +) diff --git a/pkg/protos/documentService/service.proto b/pkg/protos/documentService/service.proto new file mode 100644 index 0000000..dda3e71 --- /dev/null +++ b/pkg/protos/documentService/service.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +option go_package = ".;documentService"; + +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/pkg/protos/documentService/service_grpc.pb.go b/pkg/protos/documentService/service_grpc.pb.go new file mode 100644 index 0000000..87cb1e1 --- /dev/null +++ b/pkg/protos/documentService/service_grpc.pb.go @@ -0,0 +1,105 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc (unknown) +// source: pkg/protos/documentService/service.proto + +package documentService + +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.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// 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) { + out := new(ListDocumentsResponse) + err := c.cc.Invoke(ctx, "/DocumentService/ListDocuments", in, out, opts...) + 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. +type UnimplementedDocumentServiceServer struct { +} + +func (UnimplementedDocumentServiceServer) ListDocuments(context.Context, *ListDocumentsRequest) (*ListDocumentsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListDocuments not implemented") +} +func (UnimplementedDocumentServiceServer) mustEmbedUnimplementedDocumentServiceServer() {} + +// 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) { + 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", + } + 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: "DocumentService", + HandlerType: (*DocumentServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListDocuments", + Handler: _DocumentService_ListDocuments_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "pkg/protos/documentService/service.proto", +}