├── pkg ├── db │ ├── test │ │ └── migrations │ │ │ ├── atlas.sum │ │ │ ├── 20220914020936_changes.up.sql │ │ │ ├── 20220914055720_changes.up.sql │ │ │ ├── 20221022062934_changes.up.sql │ │ │ ├── 20221027070615_changes.up.sql │ │ │ ├── 20221120094056_changes.up.sql │ │ │ ├── 20220914020936_changes.down.sql │ │ │ ├── 20220914055720_changes.down.sql │ │ │ ├── 20221022062934_changes.down.sql │ │ │ ├── 20221027070615_changes.down.sql │ │ │ └── 20221120094056_changes.down.sql │ ├── migration_test.go │ ├── migration.go │ └── ent.go ├── ent │ ├── generate.go │ ├── schema │ │ ├── README.md │ │ ├── user.go │ │ └── pet.go │ ├── predicate │ │ └── predicate.go │ ├── runtime │ │ └── runtime.go │ ├── user │ │ ├── user.go │ │ └── where.go │ ├── context.go │ ├── runtime.go │ ├── migrate │ │ ├── schema.go │ │ └── migrate.go │ ├── config.go │ ├── pet │ │ ├── pet.go │ │ └── where.go │ ├── enttest │ │ └── enttest.go │ ├── user.go │ ├── pet_delete.go │ ├── user_delete.go │ ├── pet.go │ ├── hook │ │ └── hook.go │ ├── user_create.go │ ├── tx.go │ ├── user_update.go │ ├── pet_create.go │ └── client.go ├── http_client │ └── client.go ├── testing │ ├── ctx.go │ └── http_client.go ├── clock │ └── clock.go ├── auth │ └── context.go ├── log │ ├── file.go │ └── log.go ├── errors │ ├── errors_test.go │ └── errors.go └── otel │ └── provider.go ├── docs ├── pics │ ├── coverage.png │ ├── grafana.png │ ├── jaeger.png │ ├── metrics.png │ ├── swagger.png │ └── project_architecture.jpg └── update_swagger_ui.md ├── api ├── pet_apis │ ├── buf.yaml │ └── pet │ │ ├── rpc.proto │ │ └── pet.proto ├── ping_apis │ ├── buf.yaml │ └── ping │ │ ├── ping.proto │ │ └── rpc.proto ├── payment_apis │ ├── buf.yaml │ └── payment │ │ └── payment.proto ├── third_party_apis │ ├── buf.yaml │ ├── README.md │ └── google │ │ ├── api │ │ └── annotations.proto │ │ └── type │ │ ├── money.proto │ │ └── datetime.proto ├── buf.work.yaml ├── .buf.ignore.yaml └── buf.gen.yaml ├── .gitignore ├── internal ├── config │ ├── log.go │ ├── daemon.go │ ├── server.go │ ├── database.go │ └── config.go ├── app │ ├── daemon.go │ ├── gw_options.go │ └── kernel.go ├── middleware │ ├── prometheus │ │ └── interceptor.go │ ├── otel │ │ └── intercepter.go │ ├── recover │ │ └── interceptor.go │ ├── validate │ │ └── interceptor.go │ ├── auth │ │ └── interceptor.go │ ├── error │ │ └── interceptor.go │ └── log │ │ └── interceptor.go ├── service │ ├── pet │ │ ├── delete_pet.go │ │ ├── put_pet.go │ │ ├── get_pet.go │ │ └── service.go │ ├── ping │ │ ├── service_test.go │ │ └── service.go │ └── register.go └── daemon │ ├── example.go │ └── daemon.go ├── deploy └── chart │ ├── app-readme.md │ ├── Chart.yaml │ ├── requirements.lock │ ├── templates │ └── server │ │ ├── service.yaml │ │ ├── service_monitor.yaml │ │ ├── configmap.yaml │ │ └── deploy.yaml │ └── values.yaml ├── common ├── workspace │ └── workspace.go └── exec │ └── exec.go ├── cmd ├── lint │ ├── main.go │ └── cmd │ │ ├── root.go │ │ ├── proto.go │ │ └── go.go ├── manage │ ├── main.go │ └── cmd │ │ ├── db_migrate │ │ ├── config.yaml │ │ ├── root.go │ │ └── generate.go │ │ ├── config │ │ ├── root.go │ │ └── generate.go │ │ ├── proto │ │ ├── root.go │ │ ├── swagger.go │ │ └── go.go │ │ └── root.go ├── migrate │ ├── main.go │ └── cmd │ │ ├── version.go │ │ ├── root.go │ │ ├── to.go │ │ ├── step.go │ │ └── diff.go ├── serve │ ├── main.go │ └── cmd │ │ ├── version.go │ │ ├── root.go │ │ └── serve.go └── client │ └── main.go ├── Dockerfile ├── .golangci.yml ├── config.yaml ├── .vscode └── settings.json ├── docker-compose.yml ├── LICENSE ├── Makefile ├── .pre-commit-config.yaml └── go.mod /pkg/db/test/migrations/atlas.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/db/test/migrations/20220914020936_changes.up.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/db/test/migrations/20220914055720_changes.up.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/db/test/migrations/20221022062934_changes.up.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/db/test/migrations/20221027070615_changes.up.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/db/test/migrations/20221120094056_changes.up.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/db/test/migrations/20220914020936_changes.down.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/db/test/migrations/20220914055720_changes.down.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/db/test/migrations/20221022062934_changes.down.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/db/test/migrations/20221027070615_changes.down.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/db/test/migrations/20221120094056_changes.down.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/pics/coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PalanQu/easycoding/HEAD/docs/pics/coverage.png -------------------------------------------------------------------------------- /docs/pics/grafana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PalanQu/easycoding/HEAD/docs/pics/grafana.png -------------------------------------------------------------------------------- /docs/pics/jaeger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PalanQu/easycoding/HEAD/docs/pics/jaeger.png -------------------------------------------------------------------------------- /docs/pics/metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PalanQu/easycoding/HEAD/docs/pics/metrics.png -------------------------------------------------------------------------------- /docs/pics/swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PalanQu/easycoding/HEAD/docs/pics/swagger.png -------------------------------------------------------------------------------- /api/pet_apis/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | breaking: 3 | use: 4 | - FILE 5 | lint: 6 | use: 7 | - DEFAULT 8 | -------------------------------------------------------------------------------- /api/ping_apis/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | breaking: 3 | use: 4 | - FILE 5 | lint: 6 | use: 7 | - DEFAULT 8 | -------------------------------------------------------------------------------- /api/payment_apis/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | breaking: 3 | use: 4 | - FILE 5 | lint: 6 | use: 7 | - DEFAULT 8 | -------------------------------------------------------------------------------- /docs/pics/project_architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PalanQu/easycoding/HEAD/docs/pics/project_architecture.jpg -------------------------------------------------------------------------------- /api/third_party_apis/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | breaking: 3 | use: 4 | - FILE 5 | lint: 6 | use: 7 | - DEFAULT 8 | -------------------------------------------------------------------------------- /api/buf.work.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | directories: 3 | - third_party_apis 4 | - payment_apis 5 | - pet_apis 6 | - ping_apis 7 | -------------------------------------------------------------------------------- /pkg/ent/generate.go: -------------------------------------------------------------------------------- 1 | package ent 2 | 3 | //go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/versioned-migration ./schema 4 | -------------------------------------------------------------------------------- /api/third_party_apis/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | ## Where are these files from 4 | 5 | https://github.com/googleapis/googleapis/tree/master/google 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Go related files. 2 | *.pb.go 3 | *.pb.gw.go 4 | *.pb.validate.go 5 | *.swagger.json 6 | 7 | # coverage file 8 | coverage.out 9 | 10 | # tgz 11 | *.tgz 12 | -------------------------------------------------------------------------------- /internal/config/log.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type LogConfig struct { 4 | Level string `mapstructure:"level" defaultvalue:"INFO"` 5 | Dir string `mapstructure:"dir" defaultvalue:""` 6 | } 7 | -------------------------------------------------------------------------------- /deploy/chart/app-readme.md: -------------------------------------------------------------------------------- 1 | # Easycoding chart 2 | 3 | helm template easycoding deploy/chart/ --values deploy/chart/values.yaml 4 | helm install -n easycoding -f deploy/chart/values.yaml easycoding deploy/chart 5 | -------------------------------------------------------------------------------- /common/workspace/workspace.go: -------------------------------------------------------------------------------- 1 | package workspace 2 | 3 | import ( 4 | "path/filepath" 5 | "runtime" 6 | ) 7 | 8 | func GetWorkspace() string { 9 | _, b, _, _ := runtime.Caller(0) 10 | return filepath.Join(filepath.Dir(b), "../..") 11 | } 12 | -------------------------------------------------------------------------------- /deploy/chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | name: easycoding 4 | version: 0.0.1 5 | appVersion: '' 6 | dependencies: 7 | - condition: mysql.enabled 8 | name: mysql 9 | repository: https://charts.bitnami.com/bitnami 10 | version: 9.2.x 11 | -------------------------------------------------------------------------------- /deploy/chart/requirements.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: mysql 3 | repository: https://charts.bitnami.com/bitnami 4 | version: 9.2.5 5 | digest: sha256:84b024abd6566caa02c79ec64b49c2d993ebc3dfe1286af16ec78e2870154ca4 6 | generated: "2022-08-05T14:53:11.047594975+08:00" 7 | -------------------------------------------------------------------------------- /internal/config/daemon.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type DaemonConfig struct { 4 | ExampleDaemon ExampleDaemon `mapstructure:"example_daemon"` 5 | } 6 | 7 | type ExampleDaemon struct { 8 | DurationSeconds int `mapstructure:"example_daemon" defaultvalue:"5"` 9 | } 10 | -------------------------------------------------------------------------------- /pkg/http_client/client.go: -------------------------------------------------------------------------------- 1 | package http_client 2 | 3 | import "net/http" 4 | 5 | type HTTPClient interface { 6 | Do(req *http.Request) (*http.Response, error) 7 | } 8 | 9 | var ( 10 | Client HTTPClient 11 | ) 12 | 13 | func Init() { 14 | Client = &http.Client{} 15 | } 16 | -------------------------------------------------------------------------------- /cmd/lint/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "easycoding/cmd/lint/cmd" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | if err := cmd.InitCmd(); err != nil { 11 | fmt.Println(err) 12 | os.Exit(1) 13 | } 14 | if err := cmd.Execute(); err != nil { 15 | fmt.Println(err) 16 | os.Exit(1) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /internal/app/daemon.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "easycoding/internal/daemon" 5 | ) 6 | 7 | func (k *Kernel) StartDaemons() { 8 | m := daemon.NewManager( 9 | k.context.ctx, 10 | k.wg, 11 | k.log, 12 | ) 13 | 14 | go m.Start(daemon.NewExampleDaemon(k.config.Daemon.ExampleDaemon.DurationSeconds)) 15 | } 16 | -------------------------------------------------------------------------------- /cmd/manage/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "easycoding/cmd/manage/cmd" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | if err := cmd.InitCmd(); err != nil { 11 | fmt.Println(err) 12 | os.Exit(1) 13 | } 14 | if err := cmd.Execute(); err != nil { 15 | fmt.Println(err) 16 | os.Exit(1) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /cmd/migrate/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "easycoding/cmd/migrate/cmd" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | if err := cmd.InitCmd(); err != nil { 11 | fmt.Println(err) 12 | os.Exit(1) 13 | } 14 | if err := cmd.Execute(); err != nil { 15 | fmt.Println(err) 16 | os.Exit(1) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /cmd/serve/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "easycoding/cmd/serve/cmd" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | if err := cmd.InitCmd(); err != nil { 11 | fmt.Println(err) 12 | os.Exit(1) 13 | } 14 | if err := cmd.Execute(); err != nil { 15 | fmt.Println(err) 16 | os.Exit(1) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM golang:1.18-alpine 4 | 5 | # go cn proxy 6 | ENV GOPROXY "https://goproxy.cn,direct" 7 | 8 | WORKDIR /app 9 | 10 | COPY . . 11 | RUN go mod download 12 | 13 | RUN go build cmd/serve/main.go 14 | 15 | EXPOSE 10000 16 | EXPOSE 10001 17 | EXPOSE 10002 18 | 19 | CMD [ "/app/main" ] 20 | -------------------------------------------------------------------------------- /api/.buf.ignore.yaml: -------------------------------------------------------------------------------- 1 | # This file is not buf builtin module, managed by cmd/manage_api/main.go 2 | 3 | # TODO(qujiabao): remove this file after buf generate support exclude dir 4 | # The following link is not the best solution 5 | # https://github.com/bufbuild/buf/issues/188 6 | 7 | ignore_modules: 8 | - google 9 | - validate 10 | -------------------------------------------------------------------------------- /pkg/ent/schema/README.md: -------------------------------------------------------------------------------- 1 | # User managed schema 2 | 3 | Run the following command in `pkg` 4 | 5 | ``` bash 6 | go run -mod=mod entgo.io/ent/cmd/ent init your_model_name 7 | ``` 8 | 9 | ``` bash 10 | go generate ./ent 11 | ``` 12 | 13 | ## Example 14 | 15 | ``` bash 16 | go run -mod=mod entgo.io/ent/cmd/ent init User 17 | ``` 18 | 19 | -------------------------------------------------------------------------------- /pkg/testing/ctx.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc/metadata" 7 | ) 8 | 9 | func CreateGrpcCtx(userID string) context.Context { 10 | m := map[string]string{ 11 | "user_id": userID, 12 | } 13 | md := metadata.New(m) 14 | return metadata.NewIncomingContext(context.Background(), md) 15 | } 16 | -------------------------------------------------------------------------------- /api/ping_apis/ping/ping.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package ping; 4 | 5 | option go_package = 'easycoding/api/ping'; 6 | 7 | import "validate/validate.proto"; 8 | 9 | message PingRequest { 10 | string req = 1[(validate.rules).string = {min_len: 0, max_len: 10}]; 11 | } 12 | 13 | message PingResponse { 14 | string res = 1; 15 | } 16 | -------------------------------------------------------------------------------- /pkg/ent/predicate/predicate.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package predicate 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql" 7 | ) 8 | 9 | // Pet is the predicate function for pet builders. 10 | type Pet func(*sql.Selector) 11 | 12 | // User is the predicate function for user builders. 13 | type User func(*sql.Selector) 14 | -------------------------------------------------------------------------------- /pkg/ent/runtime/runtime.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package runtime 4 | 5 | // The schema-stitching logic is generated in easycoding/pkg/ent/runtime.go 6 | 7 | const ( 8 | Version = "v0.11.2" // Version of ent codegen. 9 | Sum = "h1:UM2/BUhF2FfsxPHRxLjQbhqJNaDdVlOwNIAMLs2jyto=" // Sum of ent codegen. 10 | ) 11 | -------------------------------------------------------------------------------- /internal/config/server.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type ServerConfig struct { 4 | GatewayPort string `mapstructure:"gateway_port" defaultvalue:"10000"` 5 | GrpcPort string `mapstructure:"grpc_port" defaultvalue:"10001"` 6 | SwaggerPort string `mapstructure:"swagger_port" defaultvalue:"10002"` 7 | RestartOnError bool `mapstructure:"restart_on_error" defaultvalue:"false"` 8 | } 9 | -------------------------------------------------------------------------------- /api/ping_apis/ping/rpc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package ping; 4 | 5 | option go_package = 'easycoding/api/ping'; 6 | 7 | import "google/api/annotations.proto"; 8 | import "ping/ping.proto"; 9 | 10 | // The ping service definition. 11 | service PingSvc { 12 | rpc Ping(PingRequest) returns (PingResponse) { 13 | option (google.api.http) = { 14 | get: "/ping", 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/middleware/prometheus/interceptor.go: -------------------------------------------------------------------------------- 1 | package prometheus 2 | 3 | import ( 4 | "context" 5 | 6 | grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" 7 | "google.golang.org/grpc" 8 | ) 9 | 10 | func Interceptor() func( 11 | ctx context.Context, 12 | req interface{}, 13 | _ *grpc.UnaryServerInfo, 14 | handler grpc.UnaryHandler) (interface{}, error) { 15 | return grpc_prometheus.UnaryServerInterceptor 16 | } 17 | -------------------------------------------------------------------------------- /internal/middleware/otel/intercepter.go: -------------------------------------------------------------------------------- 1 | package otel 2 | 3 | import ( 4 | "context" 5 | 6 | "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 7 | "google.golang.org/grpc" 8 | ) 9 | 10 | func Interceptor() func( 11 | ctx context.Context, 12 | req interface{}, 13 | _ *grpc.UnaryServerInfo, 14 | handler grpc.UnaryHandler, 15 | ) (interface{}, error) { 16 | return otelgrpc.UnaryServerInterceptor() 17 | } 18 | -------------------------------------------------------------------------------- /cmd/serve/cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "easycoding/internal/app" 5 | "fmt" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var versionCmd = &cobra.Command{ 11 | Use: "version", 12 | Short: "Print the current version", 13 | Run: func(cmd *cobra.Command, args []string) { 14 | fmt.Println(app.Version()) 15 | }, 16 | } 17 | 18 | func initVersion() error { 19 | rootCmd.AddCommand(versionCmd) 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /docs/update_swagger_ui.md: -------------------------------------------------------------------------------- 1 | # update swagger ui 2 | 3 | https://github.com/elazarl/go-bindata-assetfs 4 | 5 | ``` bash 6 | go get github.com/go-bindata/go-bindata/... 7 | go get github.com/elazarl/go-bindata-assetfs/... 8 | ``` 9 | 10 | git clone https://github.com/swagger-api/swagger-ui 11 | 12 | update url in swagger-initializer.js from https://petstore.swagger.io/v2/swagger.json to ./api.swagger.json 13 | 14 | go-bindata-assetfs dist/... 15 | -------------------------------------------------------------------------------- /pkg/ent/schema/user.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | ) 7 | 8 | // User holds the schema definition for the User entity. 9 | type User struct { 10 | ent.Schema 11 | } 12 | 13 | // Fields of the User. 14 | func (User) Fields() []ent.Field { 15 | return []ent.Field{ 16 | field.String("name"), 17 | } 18 | } 19 | 20 | // Edges of the User. 21 | func (User) Edges() []ent.Edge { 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /internal/service/pet/delete_pet.go: -------------------------------------------------------------------------------- 1 | package pet 2 | 3 | import ( 4 | "context" 5 | pet_pb "easycoding/api/pet" 6 | "easycoding/pkg/errors" 7 | ) 8 | 9 | func (s *service) deletePet( 10 | ctx context.Context, 11 | req *pet_pb.DeletePetRequest, 12 | ) (*pet_pb.DeletePetResponse, error) { 13 | if err := s.DB.Pet.DeleteOneID(int(req.PetId)).Exec(ctx); err != nil { 14 | return nil, errors.ErrInvalid(err) 15 | } 16 | return &pet_pb.DeletePetResponse{}, nil 17 | } 18 | -------------------------------------------------------------------------------- /internal/service/ping/service_test.go: -------------------------------------------------------------------------------- 1 | package ping 2 | 3 | import ( 4 | "context" 5 | ping_pb "easycoding/api/ping" 6 | "testing" 7 | ) 8 | 9 | func TestPing(t *testing.T) { 10 | s := &service{} 11 | resp, err := s.Ping(context.Background(), &ping_pb.PingRequest{ 12 | Req: "", 13 | }) 14 | if err != nil { 15 | t.Errorf("Ping error %s", err.Error()) 16 | } 17 | respMsg := "pong" 18 | if resp.Res != respMsg { 19 | t.Errorf("Response error, expect %s, got %s", respMsg, resp.Res) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /internal/config/database.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type DatabaseConfig struct { 4 | Host string `mapstructure:"host" defaultvalue:"localhost"` 5 | Port string `mapstructure:"port" defaultvalue:"3306"` 6 | User string `mapstructure:"user" defaultvalue:"root"` 7 | Password string `mapstructure:"password" defaultvalue:"123456"` 8 | DBName string `mapstructure:"db_name" defaultvalue:"test"` 9 | CreateDatabase bool `mapstructure:"create_database" defaultvalue:"true"` 10 | } 11 | -------------------------------------------------------------------------------- /api/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | plugins: 3 | - name: go 4 | out: . 5 | opt: paths=source_relative 6 | - name: go-grpc 7 | out: . 8 | opt: 9 | - paths=source_relative 10 | - require_unimplemented_servers=false 11 | - name: grpc-gateway 12 | out: . 13 | opt: 14 | - paths=source_relative 15 | - generate_unbound_methods=true 16 | - name: openapiv2 17 | out: . 18 | - name: validate 19 | out: . 20 | opt: 21 | - paths=source_relative 22 | - lang=go 23 | -------------------------------------------------------------------------------- /cmd/manage/cmd/db_migrate/config.yaml: -------------------------------------------------------------------------------- 1 | database: 2 | create_database: true 3 | db_name: _migration_dev 4 | host: localhost 5 | password: root 6 | port: "3306" 7 | user: root 8 | dev_database: 9 | create_database: true 10 | db_name: _migrate_dev 11 | host: localhost 12 | password: root 13 | port: "3306" 14 | user: root 15 | log: 16 | dir: "" 17 | level: INFO 18 | server: 19 | gateway_port: "10000" 20 | grpc_port: "10001" 21 | restart_on_error: false 22 | swagger_port: "10002" 23 | -------------------------------------------------------------------------------- /deploy/chart/templates/server/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ .Values.server.service_name }} 6 | labels: 7 | app: easycoding-server 8 | app_type: grpc-go 9 | spec: 10 | type: ClusterIP 11 | ports: 12 | - name: rest-port 13 | port: {{ .Values.server.rest_port }} 14 | - name: grpc-port 15 | port: {{ .Values.server.grpc_port }} 16 | - name: swagger-port 17 | port: {{ .Values.server.swagger_port }} 18 | selector: 19 | app: easycoding-server 20 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | govet: 3 | check-shadowing: true 4 | enable: 5 | - fieldalignment 6 | gocyclo: 7 | min-complexity: 15 8 | maligned: 9 | suggest-new: true 10 | lll: 11 | line-length: 160 12 | goimports: 13 | local-prefixes: easycoding 14 | gocritic: 15 | enabled-tags: 16 | - diagnostic 17 | - performance 18 | - style 19 | - experimental 20 | disabled-checks: 21 | - hugeParam 22 | - whyNoLint 23 | 24 | run: 25 | deadline: 10m 26 | skip-dirs: 27 | - api 28 | - build 29 | -------------------------------------------------------------------------------- /deploy/chart/templates/server/service_monitor.yaml: -------------------------------------------------------------------------------- 1 | # https://grafana.com/grafana/dashboards/9186 2 | # https://grafana.com/grafana/dashboards/14765 3 | {{ if .Values.server.service_monitor }} 4 | --- 5 | apiVersion: monitoring.coreos.com/v1 6 | kind: ServiceMonitor 7 | metadata: 8 | name: easycoding-server 9 | spec: 10 | endpoints: 11 | - interval: 30s 12 | port: swagger-port 13 | namespaceSelector: 14 | matchNames: 15 | - easycoding 16 | selector: 17 | matchLabels: 18 | app: easycoding-server 19 | jobLabel: app_type 20 | {{ end }} 21 | -------------------------------------------------------------------------------- /pkg/ent/schema/pet.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "time" 5 | 6 | "entgo.io/ent" 7 | "entgo.io/ent/schema/field" 8 | ) 9 | 10 | // Pet holds the schema definition for the Pet entity. 11 | type Pet struct { 12 | ent.Schema 13 | } 14 | 15 | // Fields of the Pet. 16 | func (Pet) Fields() []ent.Field { 17 | return []ent.Field{ 18 | field.String("name").NotEmpty(), 19 | field.Int8("type").NonNegative(), 20 | field.Time("create_at").Default(time.Now()), 21 | } 22 | } 23 | 24 | // Edges of the Pet. 25 | func (Pet) Edges() []ent.Edge { 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | daemon: 2 | example_daemon: 3 | example_daemon: "5" 4 | database: 5 | create_database: true 6 | db_name: test 7 | host: localhost 8 | password: root 9 | port: "3306" 10 | user: root 11 | dev_database: 12 | create_database: true 13 | db_name: _migrate_dev 14 | host: localhost 15 | password: root 16 | port: "3306" 17 | user: root 18 | log: 19 | dir: "" 20 | level: INFO 21 | server: 22 | gateway_port: "10000" 23 | grpc_port: "10001" 24 | restart_on_error: false 25 | swagger_port: "10002" 26 | -------------------------------------------------------------------------------- /pkg/clock/clock.go: -------------------------------------------------------------------------------- 1 | package clock 2 | 3 | import "time" 4 | 5 | // Clock is a wrapper around time functions to make 6 | // time dependent code easily testable. 7 | type Clock struct { 8 | now time.Time 9 | } 10 | 11 | // FromTime returns a new clock with a set time. 12 | func FromTime(t time.Time) *Clock { 13 | return &Clock{now: t} 14 | } 15 | 16 | // Return the current time. 17 | func (c *Clock) Now() time.Time { 18 | if c == nil { 19 | return time.Now() 20 | } 21 | return c.now 22 | } 23 | 24 | // Set the current time. 25 | func (c *Clock) SetNow(t time.Time) { 26 | c.now = t 27 | } 28 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.rulers": [ 3 | 72, 4 | 80 5 | ], 6 | "editor.tabSize": 4, 7 | "files.watcherExclude": { 8 | "**/.git/": true, 9 | "**/build/**": true 10 | }, 11 | "go.lintFlags": [ 12 | "--fast" 13 | ], 14 | "go.lintTool": "golangci-lint", 15 | "go.useLanguageServer": true, 16 | "search.exclude": { 17 | "build/**/*": true 18 | }, 19 | "vim.easymotion": true, 20 | "vim.handleKeys": { 21 | "": false, 22 | "": false 23 | }, 24 | "vim.leader": "," 25 | } 26 | -------------------------------------------------------------------------------- /internal/service/pet/put_pet.go: -------------------------------------------------------------------------------- 1 | package pet 2 | 3 | import ( 4 | "context" 5 | pet_pb "easycoding/api/pet" 6 | ) 7 | 8 | func (s *service) putPet( 9 | ctx context.Context, 10 | req *pet_pb.PutPetRequest, 11 | ) (*pet_pb.PutPetResponse, error) { 12 | pet, err := s.DB.Pet.Create(). 13 | SetName(req.Name). 14 | SetType(int8(req.PetType)). 15 | Save(ctx) 16 | if err != nil { 17 | return nil, err 18 | } 19 | return &pet_pb.PutPetResponse{ 20 | Pet: &pet_pb.Pet{ 21 | PetId: int32(pet.ID), 22 | Name: pet.Name, 23 | PetType: pet_pb.PetType(pet.Type), 24 | }, 25 | }, nil 26 | } 27 | -------------------------------------------------------------------------------- /internal/service/pet/get_pet.go: -------------------------------------------------------------------------------- 1 | package pet 2 | 3 | import ( 4 | "context" 5 | pet_pb "easycoding/api/pet" 6 | "easycoding/pkg/errors" 7 | ) 8 | 9 | func (s *service) getPet( 10 | ctx context.Context, 11 | req *pet_pb.GetPetRequest, 12 | ) (*pet_pb.GetPetResponse, error) { 13 | pet, err := s.DB.Pet.Get(ctx, int(req.PetId)) 14 | if err != nil { 15 | return nil, errors.WithMessage(err, "get pet failed") 16 | } 17 | return &pet_pb.GetPetResponse{ 18 | Pet: &pet_pb.Pet{ 19 | PetId: int32(pet.ID), 20 | Name: pet.Name, 21 | PetType: pet_pb.PetType(pet.Type), 22 | }, 23 | }, nil 24 | } 25 | -------------------------------------------------------------------------------- /pkg/auth/context.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "context" 5 | "easycoding/pkg/ent" 6 | "easycoding/pkg/errors" 7 | ) 8 | 9 | type ctxAuthMarker struct{} 10 | 11 | var ( 12 | ctxAuthKey = &ctxAuthMarker{} 13 | ) 14 | 15 | func ToContext(ctx context.Context, userInfo *ent.User) context.Context { 16 | return context.WithValue(ctx, ctxAuthKey, userInfo) 17 | } 18 | 19 | func ExtractContext(ctx context.Context) (*ent.User, error) { 20 | user, ok := ctx.Value(ctxAuthKey).(*ent.User) 21 | if !ok || user == nil { 22 | return nil, errors.ErrUnauthorizedRaw("no authInfo in ctx") 23 | } 24 | return user, nil 25 | } 26 | -------------------------------------------------------------------------------- /cmd/lint/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var dryrun bool 11 | var rootCmd = &cobra.Command{ 12 | Use: "lint", 13 | Short: "Lint source code", 14 | Run: func(cmd *cobra.Command, args []string) { 15 | err := cmd.Help() 16 | if err != nil { 17 | fmt.Println(err) 18 | } 19 | os.Exit(1) 20 | }, 21 | } 22 | 23 | func InitCmd() error { 24 | rootCmd.PersistentFlags().BoolVarP(&dryrun, "dry-run", "n", false, "--dry-run") 25 | initGo() 26 | initProto() 27 | return nil 28 | } 29 | 30 | func Execute() error { 31 | return rootCmd.Execute() 32 | } 33 | -------------------------------------------------------------------------------- /api/payment_apis/payment/payment.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package payment; 4 | 5 | option go_package = 'easycoding/api/payment'; 6 | 7 | import "google/type/money.proto"; 8 | 9 | // PaymentProvider represents the supported set 10 | // of payment providers. 11 | enum PaymentProvider { 12 | PAYMENT_PROVIDER_UNSPECIFIED = 0; 13 | PAYMENT_PROVIDER_STRIPE = 1; 14 | PAYMENT_PROVIDER_PAYPAL = 2; 15 | PAYMENT_PROVIDER_APPLE = 3; 16 | } 17 | 18 | // Order represents a monetary order. 19 | message Order { 20 | string order_id = 1; 21 | string recipient_id = 2; 22 | google.type.Money amount = 3; 23 | PaymentProvider payment_provider = 4; 24 | } 25 | -------------------------------------------------------------------------------- /deploy/chart/values.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | server: 3 | image: registry.cn-hongkong.aliyuncs.com/palanqu/easycoding:v0.0.2 4 | service_name: easycoding-server 5 | rest_port: 10000 6 | grpc_port: 10001 7 | swagger_port: 10002 8 | restart_on_error: false 9 | log_dir: "" 10 | log_level: INFO 11 | service_monitor: true 12 | 13 | # values for https://artifacthub.io/packages/helm/bitnami/mysql 14 | mysql: 15 | enabled: true 16 | auth: 17 | user: root 18 | rootPassword: root 19 | database: test 20 | primary: 21 | persistence: 22 | storageClass: "rook-ceph-block" 23 | size: 8Gi 24 | service: 25 | name: easycoding-mysql 26 | ports: 27 | mysql: 3306 28 | -------------------------------------------------------------------------------- /api/pet_apis/pet/rpc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package pet; 4 | 5 | option go_package = 'easycoding/api/pet'; 6 | 7 | import "google/api/annotations.proto"; 8 | import "pet/pet.proto"; 9 | 10 | service PetStoreSvc { 11 | rpc GetPet(GetPetRequest) returns (GetPetResponse) { 12 | option (google.api.http) = { 13 | get: "/v1/pet", 14 | }; 15 | } 16 | rpc PutPet(PutPetRequest) returns (PutPetResponse) { 17 | option (google.api.http) = { 18 | put: "/v1/pet", 19 | body: "*", 20 | }; 21 | } 22 | rpc DeletePet(DeletePetRequest) returns (DeletePetResponse) { 23 | option (google.api.http) = { 24 | delete: "/v1/pet", 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /cmd/manage/cmd/config/root.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | const ( 10 | dryrunKeyName = "dry-run" 11 | ) 12 | 13 | var configCmd = &cobra.Command{ 14 | Use: "config", 15 | Short: "Manage the config.yaml files", 16 | Run: func(cmd *cobra.Command, args []string) { 17 | if len(args) == 0 { 18 | cmd.Help() 19 | os.Exit(0) 20 | } 21 | }, 22 | } 23 | 24 | func Init(rootCmd *cobra.Command) { 25 | rootCmd.AddCommand(configCmd) 26 | initGenerate() 27 | } 28 | 29 | func getDryrun(cmd *cobra.Command) bool { 30 | dryrun := true 31 | var err error 32 | dryrun, err = cmd.Flags().GetBool("dry-run") 33 | if err != nil { 34 | return true 35 | } 36 | return dryrun 37 | } 38 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | server: 3 | build: . 4 | ports: 5 | - "10000:10000" 6 | - "10001:10001" 7 | - "10002:10002" 8 | depends_on: 9 | mysql: 10 | condition: service_healthy 11 | links: 12 | - "mysql:mysql" 13 | mysql: 14 | image: mysql:8.0.22 15 | ports: 16 | - "3306:3306" 17 | environment: 18 | - MYSQL_ROOT_PASSWORD=root 19 | volumes: 20 | - easycoding_mysql:/var/lib/mysql 21 | healthcheck: 22 | test: ['CMD', 'mysqladmin' ,'ping', '-h', 'localhost', '-u', 'root', '--password=$$MYSQL_ROOT_PASSWORD'] 23 | timeout: 3s 24 | retries: 5 25 | interval: 2s 26 | 27 | volumes: 28 | easycoding_mysql: 29 | external: false 30 | -------------------------------------------------------------------------------- /cmd/manage/cmd/proto/root.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | const ( 10 | dryrunKeyName = "dry-run" 11 | ) 12 | 13 | var protoCmd = &cobra.Command{ 14 | Use: "proto", 15 | Short: "Manage the api/*.proto files", 16 | Run: func(cmd *cobra.Command, args []string) { 17 | if len(args) == 0 { 18 | cmd.Help() 19 | os.Exit(1) 20 | } 21 | }, 22 | } 23 | 24 | func getDryrun(cmd *cobra.Command) bool { 25 | dryrun := true 26 | var err error 27 | dryrun, err = cmd.Flags().GetBool("dry-run") 28 | if err != nil { 29 | return true 30 | } 31 | return dryrun 32 | } 33 | 34 | func Init(rootCmd *cobra.Command) { 35 | rootCmd.AddCommand(protoCmd) 36 | initGo() 37 | initSwagger() 38 | } 39 | -------------------------------------------------------------------------------- /internal/middleware/recover/interceptor.go: -------------------------------------------------------------------------------- 1 | package recover 2 | 3 | import ( 4 | "context" 5 | "easycoding/pkg/errors" 6 | "fmt" 7 | "runtime" 8 | 9 | recovery_middleware "github.com/grpc-ecosystem/go-grpc-middleware/recovery" 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | func recoverFunc(p interface{}) (err error) { 14 | _, f1, l1, _ := runtime.Caller(6) 15 | _, f2, l2, _ := runtime.Caller(7) 16 | detail := fmt.Sprintf("%v\n%s, %d\n%s,%d", p, f1, l1, f2, l2) 17 | return errors.ErrInternalRaw(detail) 18 | } 19 | 20 | func Interceptor() func( 21 | ctx context.Context, 22 | req interface{}, 23 | _ *grpc.UnaryServerInfo, 24 | handler grpc.UnaryHandler) (interface{}, error) { 25 | return recovery_middleware.UnaryServerInterceptor( 26 | recovery_middleware.WithRecoveryHandler(recoverFunc)) 27 | } 28 | -------------------------------------------------------------------------------- /cmd/manage/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | config_cmd "easycoding/cmd/manage/cmd/config" 7 | migrate_cmd "easycoding/cmd/manage/cmd/db_migrate" 8 | proto_cmd "easycoding/cmd/manage/cmd/proto" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var dryrun bool 14 | 15 | var rootCmd = &cobra.Command{ 16 | Use: "manage api", 17 | Short: "Manage the apis", 18 | Run: func(cmd *cobra.Command, args []string) { 19 | if len(args) == 0 { 20 | cmd.Help() 21 | os.Exit(0) 22 | } 23 | }, 24 | } 25 | 26 | func InitCmd() error { 27 | rootCmd.PersistentFlags().BoolVarP(&dryrun, "dry-run", "n", false, "--dry-run") 28 | proto_cmd.Init(rootCmd) 29 | config_cmd.Init(rootCmd) 30 | migrate_cmd.Init(rootCmd) 31 | return nil 32 | } 33 | 34 | func Execute() error { 35 | return rootCmd.Execute() 36 | } 37 | -------------------------------------------------------------------------------- /cmd/manage/cmd/config/generate.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "easycoding/internal/config" 5 | "fmt" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | type user struct { 12 | Name string `json:"name"` 13 | Age string `json:"age"` 14 | } 15 | 16 | var generateCmd = &cobra.Command{ 17 | Use: "generate", 18 | Short: "Generate config.yaml file", 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | dryrun := getDryrun(cmd) 21 | config.LoadConfig("config.yaml") 22 | if dryrun { 23 | // TODO(qujiabao): print pretty 24 | fmt.Println(viper.AllSettings()) 25 | return nil 26 | } 27 | if err := viper.WriteConfig(); err != nil { 28 | return err 29 | } 30 | 31 | return nil 32 | }, 33 | } 34 | 35 | func initGenerate() { 36 | configCmd.AddCommand(generateCmd) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/testing/http_client.go: -------------------------------------------------------------------------------- 1 | package testing 2 | 3 | import ( 4 | "bytes" 5 | "easycoding/pkg/http_client" 6 | "net/http" 7 | ) 8 | 9 | type Dofunc func(*http.Request) (*http.Response, error) 10 | 11 | type HTTPMockClient struct { 12 | DoFunc func(*http.Request) (*http.Response, error) 13 | } 14 | 15 | func (m *HTTPMockClient) Do(req *http.Request) (*http.Response, error) { 16 | return m.DoFunc(req) 17 | } 18 | 19 | func NewHttpMockClient(f Dofunc) *HTTPMockClient { 20 | return &HTTPMockClient{f} 21 | } 22 | 23 | type BodyReader struct { 24 | *bytes.Reader 25 | } 26 | 27 | func (*BodyReader) Close() error { 28 | return nil 29 | } 30 | 31 | func NewBodyReader(b []byte) *BodyReader { 32 | return &BodyReader{bytes.NewReader(b)} 33 | } 34 | 35 | func SetupHttpClient(f Dofunc) { 36 | http_client.Client = NewHttpMockClient(f) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/ent/user/user.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package user 4 | 5 | const ( 6 | // Label holds the string label denoting the user type in the database. 7 | Label = "user" 8 | // FieldID holds the string denoting the id field in the database. 9 | FieldID = "id" 10 | // FieldName holds the string denoting the name field in the database. 11 | FieldName = "name" 12 | // Table holds the table name of the user in the database. 13 | Table = "users" 14 | ) 15 | 16 | // Columns holds all SQL columns for user fields. 17 | var Columns = []string{ 18 | FieldID, 19 | FieldName, 20 | } 21 | 22 | // ValidColumn reports if the column name is valid (part of the table columns). 23 | func ValidColumn(column string) bool { 24 | for i := range Columns { 25 | if column == Columns[i] { 26 | return true 27 | } 28 | } 29 | return false 30 | } 31 | -------------------------------------------------------------------------------- /common/exec/exec.go: -------------------------------------------------------------------------------- 1 | package exec 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | 9 | "github.com/fatih/color" 10 | quote "github.com/kballard/go-shellquote" 11 | ) 12 | 13 | func ExecCommand(command []string, cwd string, dryRun bool) error { 14 | cmd := exec.Command(command[0], command[1:]...) 15 | 16 | if cwd == "" { 17 | curr, err := os.Getwd() 18 | if err != nil { 19 | return err 20 | } 21 | cwd = curr 22 | } 23 | cmd.Dir = cwd 24 | color.Cyan("pushd " + cwd) 25 | color.Cyan("\t" + quote.Join(cmd.Args...)) 26 | color.Cyan("popd") 27 | 28 | if dryRun { 29 | return nil 30 | } 31 | 32 | var errb bytes.Buffer 33 | var outb bytes.Buffer 34 | cmd.Stderr = &errb 35 | cmd.Stdout = &outb 36 | if err := cmd.Run(); err != nil { 37 | return fmt.Errorf(err.Error(), outb.String(), errb.String()) 38 | } 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /cmd/lint/cmd/proto.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "easycoding/common/exec" 5 | "easycoding/common/workspace" 6 | "fmt" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | const apiPath = "api" 15 | 16 | var protoCmd = &cobra.Command{ 17 | Use: "proto", 18 | Short: "Lint proto source code", 19 | Run: func(cmd *cobra.Command, args []string) { 20 | files := args 21 | commands := []string{"buf", "lint"} 22 | for _, file := range files { 23 | newPath := strings.TrimPrefix(file, fmt.Sprintf("%s/", apiPath)) 24 | commands = append(commands, "--path", newPath) 25 | } 26 | cwd := filepath.Join(workspace.GetWorkspace(), "api") 27 | if err := exec.ExecCommand(commands, cwd, dryrun); err != nil { 28 | fmt.Println(err.Error()) 29 | os.Exit(1) 30 | } 31 | }, 32 | } 33 | 34 | func initProto() { 35 | rootCmd.AddCommand(protoCmd) 36 | } 37 | -------------------------------------------------------------------------------- /deploy/chart/templates/server/configmap.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: easycoding-server-config 6 | labels: 7 | app: easycoding-server 8 | data: 9 | config.yaml: | 10 | database: 11 | db_name: test 12 | host: {{ .Values.mysql.primary.service.name }} 13 | password: {{ .Values.mysql.auth.rootPassword | quote }} 14 | port: {{ .Values.mysql.primary.service.ports.mysql | quote }} 15 | user: {{ .Values.mysql.auth.user }} 16 | log: 17 | dir: {{ .Values.server.log_dir }} 18 | level: {{ .Values.server.log_level }} 19 | server: 20 | gateway_port: {{ .Values.server.rest_port | quote }} 21 | grpc_port: {{ .Values.server.grpc_port | quote }} 22 | restart_on_error: {{ .Values.server.restart_on_error }} 23 | swagger_port: {{ .Values.server.swagger_port | quote }} 24 | -------------------------------------------------------------------------------- /api/pet_apis/pet/pet.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package pet; 4 | 5 | option go_package = 'easycoding/api/pet'; 6 | 7 | import "payment/payment.proto"; 8 | 9 | // PetType represents the different types of pets in the pet store. 10 | enum PetType { 11 | PET_TYPE_UNSPECIFIED = 0; 12 | PET_TYPE_CAT = 1; 13 | PET_TYPE_DOG = 2; 14 | PET_TYPE_SNAKE = 3; 15 | PET_TYPE_HAMSTER = 4; 16 | } 17 | 18 | // Pet represents a pet in the pet store. 19 | message Pet { 20 | int32 pet_id = 1; 21 | string name = 2; 22 | PetType pet_type = 3; 23 | } 24 | 25 | message GetPetRequest { 26 | int32 pet_id = 1; 27 | } 28 | 29 | message GetPetResponse { 30 | Pet pet = 1; 31 | } 32 | 33 | message PutPetRequest { 34 | PetType pet_type = 1; 35 | string name = 2; 36 | } 37 | 38 | message PutPetResponse { 39 | Pet pet = 1; 40 | } 41 | 42 | message DeletePetRequest { 43 | int32 pet_id = 1; 44 | } 45 | 46 | message DeletePetResponse {} 47 | -------------------------------------------------------------------------------- /internal/daemon/example.go: -------------------------------------------------------------------------------- 1 | package daemon 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | // Example daemon 12 | type Example struct { 13 | periodSeconds int 14 | } 15 | 16 | // NewExample returns a new example daemon. 17 | func NewExampleDaemon(periodSeconds int) *Example { 18 | return &Example{ 19 | periodSeconds: periodSeconds, 20 | } 21 | } 22 | 23 | func (g *Example) Name() string { return "example" } 24 | 25 | // Example daemon logs to the console every few seconds. 26 | func (g *Example) Run(ctx context.Context, wg *sync.WaitGroup, logger *logrus.Logger) error { 27 | wg.Add(1) 28 | defer wg.Done() 29 | go func() { 30 | for { 31 | select { 32 | case <-time.After(time.Duration(g.periodSeconds) * time.Second): 33 | // do something 34 | case <-ctx.Done(): 35 | logger.Debug("example daemon is shutting down...") 36 | return 37 | } 38 | } 39 | }() 40 | <-ctx.Done() 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /cmd/serve/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "easycoding/internal/app" 5 | "log" 6 | "os" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var ( 12 | configPath string 13 | ) 14 | 15 | var rootCmd = &cobra.Command{ 16 | Use: "go-template-server", 17 | Short: "This is the main command", 18 | Run: func(cmd *cobra.Command, args []string) { 19 | runServer(cmd, args) 20 | }, 21 | } 22 | 23 | func InitCmd() error { 24 | rootCmd.PersistentFlags().StringVar( 25 | &configPath, "config", "./config.yaml", "--config") 26 | if err := initServe(); err != nil { 27 | return err 28 | } 29 | if err := initVersion(); err != nil { 30 | return err 31 | } 32 | return nil 33 | } 34 | 35 | func Execute() error { 36 | return rootCmd.Execute() 37 | } 38 | 39 | func boot() *app.Kernel { 40 | kernel, err := app.New(configPath) 41 | if err != nil { 42 | logger := log.New(os.Stderr, "debug", 1) 43 | logger.Fatalf("failed to boot kernel: %s", err) 44 | os.Exit(2) 45 | } 46 | return kernel 47 | } 48 | -------------------------------------------------------------------------------- /pkg/ent/context.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | type clientCtxKey struct{} 10 | 11 | // FromContext returns a Client stored inside a context, or nil if there isn't one. 12 | func FromContext(ctx context.Context) *Client { 13 | c, _ := ctx.Value(clientCtxKey{}).(*Client) 14 | return c 15 | } 16 | 17 | // NewContext returns a new context with the given Client attached. 18 | func NewContext(parent context.Context, c *Client) context.Context { 19 | return context.WithValue(parent, clientCtxKey{}, c) 20 | } 21 | 22 | type txCtxKey struct{} 23 | 24 | // TxFromContext returns a Tx stored inside a context, or nil if there isn't one. 25 | func TxFromContext(ctx context.Context) *Tx { 26 | tx, _ := ctx.Value(txCtxKey{}).(*Tx) 27 | return tx 28 | } 29 | 30 | // NewTxContext returns a new context with the given Tx attached. 31 | func NewTxContext(parent context.Context, tx *Tx) context.Context { 32 | return context.WithValue(parent, txCtxKey{}, tx) 33 | } 34 | -------------------------------------------------------------------------------- /internal/middleware/validate/interceptor.go: -------------------------------------------------------------------------------- 1 | package validate 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | ) 10 | 11 | type validator interface { 12 | Validate() error 13 | } 14 | 15 | func Interceptor() func( 16 | ctx context.Context, 17 | req interface{}, 18 | _ *grpc.UnaryServerInfo, 19 | handler grpc.UnaryHandler, 20 | ) (interface{}, error) { 21 | return func( 22 | ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler, 23 | ) (interface{}, error) { 24 | if v, ok := req.(validator); ok { 25 | if err := v.Validate(); err != nil { 26 | return nil, status.Errorf(codes.InvalidArgument, err.Error()) 27 | } 28 | } 29 | res, err := handler(ctx, req) 30 | if err == nil { 31 | if v, ok := res.(validator); ok { 32 | if err := v.Validate(); err != nil { 33 | return res, status.Errorf(codes.Internal, err.Error()) 34 | } 35 | } 36 | } 37 | return res, err 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cmd/manage/cmd/db_migrate/root.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "os" 5 | 6 | c "easycoding/internal/config" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | const ( 12 | defaultMigrationDir = "./migrations" 13 | defaultConfigPath = "./cmd/manage/cmd/db_migrate/config.yaml" 14 | ) 15 | 16 | var ( 17 | dryrun bool 18 | migrationDir string 19 | configPath string 20 | 21 | config *c.Config 22 | ) 23 | 24 | var dbMigrateCmd = &cobra.Command{ 25 | Use: "db-migrate", 26 | Short: "Manage database migration files", 27 | Run: func(cmd *cobra.Command, args []string) { 28 | if len(args) == 0 { 29 | cmd.Help() 30 | os.Exit(0) 31 | } 32 | }, 33 | } 34 | 35 | func Init(rootCmd *cobra.Command) { 36 | rootCmd.AddCommand(dbMigrateCmd) 37 | rootCmd.PersistentFlags().StringVarP( 38 | &migrationDir, "migrate-path", "p", defaultMigrationDir, "--migrate-path") 39 | rootCmd.PersistentFlags().StringVar( 40 | &configPath, "config", defaultConfigPath, "--config") 41 | config = c.LoadConfig(configPath) 42 | initGenerate() 43 | } 44 | -------------------------------------------------------------------------------- /internal/middleware/auth/interceptor.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "context" 5 | 6 | pkg_auth "easycoding/pkg/auth" 7 | "easycoding/pkg/ent" 8 | 9 | grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth" 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/codes" 12 | "google.golang.org/grpc/status" 13 | ) 14 | 15 | func Interceptor() grpc.UnaryServerInterceptor { 16 | return grpc_auth.UnaryServerInterceptor(authInterceptor) 17 | } 18 | 19 | func authInterceptor(ctx context.Context) (context.Context, error) { 20 | token, err := grpc_auth.AuthFromMD(ctx, "Bearer") 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | userInfo, err := parseToken(token) 26 | if err != nil { 27 | return nil, status.Errorf(codes.Unauthenticated, "invalid auth token: %v", err) 28 | } 29 | 30 | newCtx := pkg_auth.ToContext(ctx, userInfo) 31 | 32 | return newCtx, nil 33 | } 34 | 35 | func parseToken(token string) (*ent.User, error) { 36 | // hard code auth func, use your own func in production 37 | return &ent.User{ 38 | ID: 123, 39 | Name: "foo", 40 | }, nil 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jiabao Qu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /internal/service/ping/service.go: -------------------------------------------------------------------------------- 1 | package ping 2 | 3 | import ( 4 | "context" 5 | ping_pb "easycoding/api/ping" 6 | 7 | "github.com/sirupsen/logrus" 8 | "go.opentelemetry.io/otel/attribute" 9 | "go.opentelemetry.io/otel/trace" 10 | ) 11 | 12 | // Service implements ping_pb.pingSrvServer. 13 | type service struct { 14 | Logger *logrus.Logger 15 | Tracer trace.Tracer 16 | } 17 | 18 | // AuthFuncOverride overrides global AuthFunc, this is used to escape from Auth 19 | // Interceptor. 20 | func (*service) AuthFuncOverride( 21 | ctx context.Context, _ string) (context.Context, error) { 22 | return ctx, nil 23 | } 24 | 25 | var _ ping_pb.PingSvcServer = (*service)(nil) 26 | 27 | func New(logger *logrus.Logger, tracer trace.Tracer) *service { 28 | return &service{ 29 | Logger: logger, 30 | Tracer: tracer, 31 | } 32 | } 33 | 34 | // Ping implements ping_pb.pingSrv.Pong 35 | func (s *service) Ping( 36 | ctx context.Context, 37 | _ *ping_pb.PingRequest, 38 | ) (*ping_pb.PingResponse, error) { 39 | _, span := s.Tracer.Start(ctx, "ping") 40 | span.SetAttributes(attribute.Key("method").String("ping")) 41 | defer span.End() 42 | return &ping_pb.PingResponse{Res: "pong"}, nil 43 | } 44 | -------------------------------------------------------------------------------- /api/third_party_apis/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/api/http.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "AnnotationsProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | extend google.protobuf.MethodOptions { 29 | // See `HttpRule`. 30 | HttpRule http = 72295728; 31 | } 32 | -------------------------------------------------------------------------------- /cmd/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | ping_pb "easycoding/api/ping" 10 | pkg_otel "easycoding/pkg/otel" 11 | 12 | "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 13 | "google.golang.org/grpc" 14 | "google.golang.org/grpc/credentials/insecure" 15 | ) 16 | 17 | func main() { 18 | address := "localhost:10001" 19 | tracer, otelShutdownFunc, err := pkg_otel.NewTracer() 20 | if err != nil { 21 | panic(err) 22 | } 23 | _, span := tracer.Start(context.Background(), "ping client") 24 | defer span.End() 25 | defer otelShutdownFunc() 26 | conn, err := grpc.Dial(address, 27 | grpc.WithTransportCredentials(insecure.NewCredentials()), 28 | grpc.WithChainUnaryInterceptor(otelgrpc.UnaryClientInterceptor()), 29 | ) 30 | if err != nil { 31 | log.Fatalf("did not connect: %v", err) 32 | } 33 | defer conn.Close() 34 | client := ping_pb.NewPingSvcClient(conn) 35 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 36 | defer cancel() 37 | r, err := client.Ping(ctx, &ping_pb.PingRequest{Req: "ping"}) 38 | if err != nil { 39 | log.Fatalf("could not greet: %v", err) 40 | } 41 | fmt.Println(r) 42 | } 43 | -------------------------------------------------------------------------------- /pkg/log/file.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/pkg/errors" 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | type LogrusFileHook struct { 12 | file *os.File 13 | flag int 14 | chmod os.FileMode 15 | formatter logrus.Formatter 16 | } 17 | 18 | func NewLogrusFileHook(file string, flag int, chmod os.FileMode) (*LogrusFileHook, error) { 19 | plainFormatter := getFormatter(true) 20 | logFile, err := os.OpenFile(file, flag, chmod) 21 | if err != nil { 22 | fmt.Fprintf(os.Stderr, "unable to write file on filehook %v", err) 23 | return nil, err 24 | } 25 | 26 | return &LogrusFileHook{logFile, flag, chmod, plainFormatter}, err 27 | } 28 | 29 | func (hook *LogrusFileHook) Fire(entry *logrus.Entry) error { 30 | plainformat, err := hook.formatter.Format(entry) 31 | if err != nil { 32 | return errors.WithStack(err) 33 | } 34 | line := string(plainformat) 35 | _, err = hook.file.WriteString(line) 36 | if err != nil { 37 | fmt.Fprintf(os.Stderr, "unable to write file on filehook (entry.String): %v", err) 38 | return err 39 | } 40 | 41 | return nil 42 | } 43 | 44 | func (hook *LogrusFileHook) Levels() []logrus.Level { 45 | return logrus.AllLevels 46 | } 47 | -------------------------------------------------------------------------------- /internal/service/pet/service.go: -------------------------------------------------------------------------------- 1 | package pet 2 | 3 | import ( 4 | "context" 5 | pet_pb "easycoding/api/pet" 6 | "easycoding/pkg/ent" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | type service struct { 12 | Logger *logrus.Logger 13 | DB *ent.Client 14 | } 15 | 16 | // AuthFuncOverride overrides global AuthFunc, this is used to escape from Auth 17 | // Interceptor. 18 | func (*service) AuthFuncOverride( 19 | ctx context.Context, _ string) (context.Context, error) { 20 | return ctx, nil 21 | } 22 | 23 | var _ pet_pb.PetStoreSvcServer = (*service)(nil) 24 | 25 | func New(logger *logrus.Logger, db *ent.Client) *service { 26 | return &service{ 27 | Logger: logger, 28 | DB: db, 29 | } 30 | } 31 | 32 | func (s *service) GetPet( 33 | ctx context.Context, 34 | req *pet_pb.GetPetRequest, 35 | ) (*pet_pb.GetPetResponse, error) { 36 | return s.getPet(ctx, req) 37 | } 38 | 39 | func (s *service) PutPet( 40 | ctx context.Context, 41 | req *pet_pb.PutPetRequest, 42 | ) (*pet_pb.PutPetResponse, error) { 43 | return s.putPet(ctx, req) 44 | } 45 | 46 | func (s *service) DeletePet( 47 | ctx context.Context, 48 | req *pet_pb.DeletePetRequest, 49 | ) (*pet_pb.DeletePetResponse, error) { 50 | return s.deletePet(ctx, req) 51 | } 52 | -------------------------------------------------------------------------------- /cmd/serve/cmd/serve.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | "time" 10 | 11 | "easycoding/internal/app" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | func initServe() error { 17 | rootCmd.AddCommand(serveCmd) 18 | return nil 19 | } 20 | 21 | var serveCmd = &cobra.Command{ 22 | Use: "serve", 23 | Short: "Start the Server", 24 | Long: `This command boots the web server and serves the application to the local network.`, 25 | Run: runServer, 26 | } 27 | 28 | func runServer(_ *cobra.Command, _ []string) { 29 | kernel := boot() 30 | 31 | go kernel.ListenGrpc() 32 | go kernel.ListenGrpcGateway() 33 | go kernel.ListenSwagger() 34 | go kernel.StartDaemons() 35 | 36 | graceful(kernel, 30*time.Second) 37 | } 38 | 39 | func graceful(instance *app.Kernel, timeout time.Duration) { 40 | stop := make(chan os.Signal, 1) 41 | 42 | signal.Notify(stop, os.Interrupt, syscall.SIGTERM) 43 | 44 | <-stop 45 | 46 | ctx, cancel := context.WithTimeout(context.Background(), timeout) 47 | defer cancel() 48 | 49 | if err := instance.Shutdown(ctx); err != nil { 50 | log.Fatalf("application shutdown error: %v\n", err) 51 | } else { 52 | log.Println("application stopped") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pkg/errors/errors_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | stderrors "errors" 5 | "testing" 6 | 7 | pkgerrors "github.com/pkg/errors" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func createNotFoundWrapErr() error { 12 | return ErrNotFound(stderrors.New("not found")) 13 | } 14 | 15 | func TestErrors(t *testing.T) { 16 | t.Run("not found error test", func(t *testing.T) { 17 | err1 := stderrors.New("not found error") 18 | newErr := ErrNotFound(err1) 19 | assert.Equal(t, "not found error: not found error", newErr.Error()) 20 | }) 21 | t.Run("not found raw error test", func(t *testing.T) { 22 | err1 := stderrors.New("not found error") 23 | newErr := ErrNotFoundf(err1, "err1 error %s", "test") 24 | assert.Equal(t, "not found error: err1 error test: not found error", newErr.Error()) 25 | }) 26 | t.Run("nested cause test", func(t *testing.T) { 27 | err := createNotFoundWrapErr() 28 | switch pkgerrors.Cause(err).(type) { 29 | case notFoundError: 30 | default: 31 | t.Error("expect to equal InvalidError type") 32 | } 33 | }) 34 | t.Run("nested not found error test", func(t *testing.T) { 35 | err := createNotFoundWrapErr() 36 | if !ErrorIs(err, NotFoundError) { 37 | t.Error("not equal") 38 | } 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/ent/runtime.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "easycoding/pkg/ent/pet" 7 | "easycoding/pkg/ent/schema" 8 | "time" 9 | ) 10 | 11 | // The init function reads all schema descriptors with runtime code 12 | // (default values, validators, hooks and policies) and stitches it 13 | // to their package variables. 14 | func init() { 15 | petFields := schema.Pet{}.Fields() 16 | _ = petFields 17 | // petDescName is the schema descriptor for name field. 18 | petDescName := petFields[0].Descriptor() 19 | // pet.NameValidator is a validator for the "name" field. It is called by the builders before save. 20 | pet.NameValidator = petDescName.Validators[0].(func(string) error) 21 | // petDescType is the schema descriptor for type field. 22 | petDescType := petFields[1].Descriptor() 23 | // pet.TypeValidator is a validator for the "type" field. It is called by the builders before save. 24 | pet.TypeValidator = petDescType.Validators[0].(func(int8) error) 25 | // petDescCreateAt is the schema descriptor for create_at field. 26 | petDescCreateAt := petFields[2].Descriptor() 27 | // pet.DefaultCreateAt holds the default value on creation for the create_at field. 28 | pet.DefaultCreateAt = petDescCreateAt.Default.(time.Time) 29 | } 30 | -------------------------------------------------------------------------------- /cmd/lint/cmd/go.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "easycoding/common/exec" 5 | "easycoding/common/workspace" 6 | "fmt" 7 | "os" 8 | "strings" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var goCmd = &cobra.Command{ 14 | Use: "go", 15 | Short: "Lint go source code", 16 | Long: `Use golangci-lint to lint golang source code, refactor 17 | golangci-lint because of typechecking error: named files must all be in 18 | one directory.`, 19 | Run: func(cmd *cobra.Command, args []string) { 20 | files := args 21 | packages := []string{} 22 | for _, file := range files { 23 | parts := strings.Split(file, "/") 24 | packageName := strings.Join(parts[0:len(parts)-1], "/") 25 | if !contains(packages, packageName) { 26 | packages = append(packages, packageName) 27 | } 28 | } 29 | 30 | commands := []string{"golangci-lint", "run"} 31 | commands = append(commands, packages...) 32 | 33 | if err := exec.ExecCommand(commands, workspace.GetWorkspace(), dryrun); err != nil { 34 | fmt.Println(err.Error()) 35 | os.Exit(1) 36 | } 37 | }, 38 | } 39 | 40 | func initGo() { 41 | rootCmd.AddCommand(goCmd) 42 | } 43 | 44 | func contains(arr []string, str string) bool { 45 | for _, s := range arr { 46 | if str == s { 47 | return true 48 | } 49 | } 50 | return false 51 | } 52 | -------------------------------------------------------------------------------- /pkg/ent/migrate/schema.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package migrate 4 | 5 | import ( 6 | "entgo.io/ent/dialect/sql/schema" 7 | "entgo.io/ent/schema/field" 8 | ) 9 | 10 | var ( 11 | // PetsColumns holds the columns for the "pets" table. 12 | PetsColumns = []*schema.Column{ 13 | {Name: "id", Type: field.TypeInt, Increment: true}, 14 | {Name: "name", Type: field.TypeString}, 15 | {Name: "type", Type: field.TypeInt8}, 16 | {Name: "create_at", Type: field.TypeTime}, 17 | } 18 | // PetsTable holds the schema information for the "pets" table. 19 | PetsTable = &schema.Table{ 20 | Name: "pets", 21 | Columns: PetsColumns, 22 | PrimaryKey: []*schema.Column{PetsColumns[0]}, 23 | } 24 | // UsersColumns holds the columns for the "users" table. 25 | UsersColumns = []*schema.Column{ 26 | {Name: "id", Type: field.TypeInt, Increment: true}, 27 | {Name: "name", Type: field.TypeString}, 28 | } 29 | // UsersTable holds the schema information for the "users" table. 30 | UsersTable = &schema.Table{ 31 | Name: "users", 32 | Columns: UsersColumns, 33 | PrimaryKey: []*schema.Column{UsersColumns[0]}, 34 | } 35 | // Tables holds all the tables in the schema. 36 | Tables = []*schema.Table{ 37 | PetsTable, 38 | UsersTable, 39 | } 40 | ) 41 | 42 | func init() { 43 | } 44 | -------------------------------------------------------------------------------- /deploy/chart/templates/server/deploy.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: easycoding-server 6 | spec: 7 | selector: 8 | matchLabels: 9 | app: easycoding-server 10 | template: 11 | metadata: 12 | labels: 13 | app: easycoding-server 14 | spec: 15 | containers: 16 | - name: easycoding-server 17 | image: {{ .Values.server.image }} 18 | imagePullPolicy: IfNotPresent 19 | ports: 20 | - name: rest-port 21 | containerPort: {{ .Values.server.rest_port }} 22 | - name: grpc-port 23 | containerPort: {{ .Values.server.grpc_port }} 24 | - name: swagger-port 25 | containerPort: {{ .Values.server.swagger_port }} 26 | volumeMounts: 27 | - name: config 28 | mountPath: /app/config.yaml 29 | subPath: config.yaml 30 | readOnly: true 31 | volumes: 32 | - name: config 33 | configMap: 34 | name: easycoding-server-config 35 | items: 36 | - key: config.yaml 37 | path: config.yaml 38 | -------------------------------------------------------------------------------- /internal/middleware/error/interceptor.go: -------------------------------------------------------------------------------- 1 | package error 2 | 3 | import ( 4 | "context" 5 | "easycoding/pkg/errors" 6 | 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | 10 | "github.com/sirupsen/logrus" 11 | "google.golang.org/grpc" 12 | ) 13 | 14 | func Interceptor(logger *logrus.Logger) grpc.UnaryServerInterceptor { 15 | return func( 16 | ctx context.Context, 17 | req interface{}, 18 | reqinfo *grpc.UnaryServerInfo, 19 | handler grpc.UnaryHandler, 20 | ) (interface{}, error) { 21 | res, err := handler(ctx, req) 22 | if err == nil { 23 | return res, err 24 | } 25 | var code codes.Code 26 | 27 | switch { 28 | case errors.ErrorIs(err, errors.InternalError): 29 | code = codes.Internal 30 | case errors.ErrorIs(err, errors.InvalidError): 31 | code = codes.InvalidArgument 32 | case errors.ErrorIs(err, errors.NotFoundError): 33 | code = codes.NotFound 34 | case errors.ErrorIs(err, errors.PermissionError): 35 | code = codes.PermissionDenied 36 | case errors.ErrorIs(err, errors.UnauthorizedError): 37 | code = codes.Unauthenticated 38 | default: 39 | logger.WithError(err).WithField("method", reqinfo.FullMethod). 40 | Warn("invalid err, without using easycoding/pkg/errors") 41 | return res, err 42 | } 43 | s := status.New(code, err.Error()) 44 | return res, s.Err() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /internal/app/gw_options.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/sirupsen/logrus" 10 | "google.golang.org/grpc/metadata" 11 | ) 12 | 13 | type readFromRequestFunc func(context.Context, *http.Request) []string 14 | 15 | var funcs = []readFromRequestFunc{ 16 | readAuthFromRequest, 17 | } 18 | 19 | func readFromRequest(logger *logrus.Logger) func(ctx context.Context, req *http.Request) metadata.MD { 20 | return func(ctx context.Context, req *http.Request) metadata.MD { 21 | data := []string{} 22 | for _, f := range funcs { 23 | pair := f(ctx, req) 24 | if len(pair)%2 == 0 { 25 | data = append(data, pair...) 26 | } else { 27 | logger.Warnf("error parsed request and header, got odd metadata %v\n", pair) 28 | } 29 | } 30 | return metadata.Pairs(data...) 31 | } 32 | } 33 | 34 | func readAuthFromRequest(ctx context.Context, req *http.Request) []string { 35 | authKey := "Authorization" 36 | authScheme := "Bearer" 37 | val := req.Header.Get(authKey) 38 | authInfo := []string{} 39 | if val == "" { 40 | return []string{} 41 | } 42 | splits := strings.SplitN(val, " ", 2) 43 | if len(splits) < 2 { 44 | return []string{} 45 | } 46 | if !strings.EqualFold(splits[0], authScheme) { 47 | return []string{} 48 | } 49 | authInfo = append(authInfo, authKey, fmt.Sprintf("%s %s", authScheme, splits[1])) 50 | return authInfo 51 | } 52 | -------------------------------------------------------------------------------- /pkg/ent/config.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "entgo.io/ent" 7 | "entgo.io/ent/dialect" 8 | ) 9 | 10 | // Option function to configure the client. 11 | type Option func(*config) 12 | 13 | // Config is the configuration for the client and its builder. 14 | type config struct { 15 | // driver used for executing database requests. 16 | driver dialect.Driver 17 | // debug enable a debug logging. 18 | debug bool 19 | // log used for logging on debug mode. 20 | log func(...interface{}) 21 | // hooks to execute on mutations. 22 | hooks *hooks 23 | } 24 | 25 | // hooks per client, for fast access. 26 | type hooks struct { 27 | Pet []ent.Hook 28 | User []ent.Hook 29 | } 30 | 31 | // Options applies the options on the config object. 32 | func (c *config) options(opts ...Option) { 33 | for _, opt := range opts { 34 | opt(c) 35 | } 36 | if c.debug { 37 | c.driver = dialect.Debug(c.driver, c.log) 38 | } 39 | } 40 | 41 | // Debug enables debug logging on the ent.Driver. 42 | func Debug() Option { 43 | return func(c *config) { 44 | c.debug = true 45 | } 46 | } 47 | 48 | // Log sets the logging function for debug mode. 49 | func Log(fn func(...interface{})) Option { 50 | return func(c *config) { 51 | c.log = fn 52 | } 53 | } 54 | 55 | // Driver configures the client driver. 56 | func Driver(driver dialect.Driver) Option { 57 | return func(c *config) { 58 | c.driver = driver 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /cmd/migrate/cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "database/sql" 5 | "easycoding/common/workspace" 6 | "easycoding/pkg/db" 7 | "fmt" 8 | "path/filepath" 9 | 10 | "github.com/golang-migrate/migrate/v4" 11 | "github.com/golang-migrate/migrate/v4/database/mysql" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var versionCmd = &cobra.Command{ 16 | Use: "version", 17 | Short: "Migrate version", 18 | RunE: func(cmd *cobra.Command, args []string) error { 19 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", 20 | config.Database.User, 21 | config.Database.Password, 22 | config.Database.Host, 23 | config.Database.Port, 24 | config.Database.DBName, 25 | ) 26 | db, err := sql.Open("mysql", dsn) 27 | if err != nil { 28 | return err 29 | } 30 | driver, err := mysql.WithInstance(db, &mysql.Config{}) 31 | if err != nil { 32 | return err 33 | } 34 | dir := filepath.Join(workspace.GetWorkspace(), migrationDir) 35 | fileUri := fmt.Sprintf("file://%s", dir) 36 | m, err := migrate.NewWithDatabaseInstance(fileUri, "mysql", driver) 37 | if err != nil { 38 | return err 39 | } 40 | v, dirty, err := m.Version() 41 | if err != nil { 42 | return err 43 | } 44 | fmt.Printf("Version: %v, Dirty: %v\n", v, dirty) 45 | return nil 46 | }, 47 | } 48 | 49 | func initVersion() error { 50 | rootCmd.AddCommand(versionCmd) 51 | if config.Database.CreateDatabase { 52 | if err := db.CreateDatabase(config); err != nil { 53 | return err 54 | } 55 | } 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # https://betterprogramming.pub/my-ultimate-makefile-for-golang-projects-fcc8ca20c9bb 2 | 3 | # TODO(qujiabao): add args to these command 4 | # docker-build 5 | # docker-release 6 | # setup 7 | 8 | # deps 9 | .PHONY: deps 10 | deps: 11 | go mod tidy 12 | 13 | 14 | # manage 15 | .PHONY: gen-config 16 | gen-config: 17 | go run cmd/manage/main.go config generate 18 | 19 | .PHONY: gen-api 20 | gen-api: 21 | go run cmd/manage/main.go proto gen-go 22 | go run cmd/manage/main.go proto gen-swagger 23 | 24 | .PHONY: clean 25 | clean: 26 | go run cmd/manage/main.go proto clean-go 27 | go run cmd/manage/main.go proto clean-swagger 28 | 29 | 30 | # build and run 31 | .PHONY: build 32 | build: gen-api 33 | go build cmd/serve/main.go 34 | 35 | .PHONY: run 36 | run: gen-api 37 | docker-compose up mysql -d 38 | sleep 3 39 | go run cmd/serve/main.go 40 | 41 | .PHONY: stop 42 | stop: 43 | docker-compose down 44 | 45 | .PHONY: run-in-docker 46 | run-in-docker: 47 | docker-compose up 48 | 49 | 50 | # test 51 | .PHONY: test 52 | test: 53 | go test ./... 54 | 55 | .PHONY: coverage 56 | coverage: 57 | go test -cover ./... 58 | 59 | .PHONY: coverage-html 60 | coverage-html: 61 | go test -coverprofile=coverage.out ./... 62 | go tool cover -html=coverage.out 63 | 64 | 65 | # generate migratation files 66 | .PHONY: migrate-generate 67 | migrate-generate: 68 | go generate ./pkg/ent 69 | go run cmd/manage/main.go db-migrate generate 70 | 71 | 72 | # lint 73 | .PHONY: lint 74 | lint: 75 | pre-commit run --all 76 | -------------------------------------------------------------------------------- /pkg/ent/pet/pet.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package pet 4 | 5 | import ( 6 | "time" 7 | ) 8 | 9 | const ( 10 | // Label holds the string label denoting the pet type in the database. 11 | Label = "pet" 12 | // FieldID holds the string denoting the id field in the database. 13 | FieldID = "id" 14 | // FieldName holds the string denoting the name field in the database. 15 | FieldName = "name" 16 | // FieldType holds the string denoting the type field in the database. 17 | FieldType = "type" 18 | // FieldCreateAt holds the string denoting the create_at field in the database. 19 | FieldCreateAt = "create_at" 20 | // Table holds the table name of the pet in the database. 21 | Table = "pets" 22 | ) 23 | 24 | // Columns holds all SQL columns for pet fields. 25 | var Columns = []string{ 26 | FieldID, 27 | FieldName, 28 | FieldType, 29 | FieldCreateAt, 30 | } 31 | 32 | // ValidColumn reports if the column name is valid (part of the table columns). 33 | func ValidColumn(column string) bool { 34 | for i := range Columns { 35 | if column == Columns[i] { 36 | return true 37 | } 38 | } 39 | return false 40 | } 41 | 42 | var ( 43 | // NameValidator is a validator for the "name" field. It is called by the builders before save. 44 | NameValidator func(string) error 45 | // TypeValidator is a validator for the "type" field. It is called by the builders before save. 46 | TypeValidator func(int8) error 47 | // DefaultCreateAt holds the default value on creation for the "create_at" field. 48 | DefaultCreateAt time.Time 49 | ) 50 | -------------------------------------------------------------------------------- /internal/service/register.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 7 | "github.com/sirupsen/logrus" 8 | "go.opentelemetry.io/otel/trace" 9 | "google.golang.org/grpc" 10 | 11 | pet_pb "easycoding/api/pet" 12 | ping_pb "easycoding/api/ping" 13 | pet_svc "easycoding/internal/service/pet" 14 | ping_svc "easycoding/internal/service/ping" 15 | "easycoding/pkg/ent" 16 | ) 17 | 18 | const ( 19 | // 500 M 20 | // TODO: add to config 21 | maxMsgSize = 500 * 1024 * 1024 22 | ) 23 | 24 | type RegisterHandlerFromEndpoint func( 25 | ctx context.Context, 26 | gwmux *runtime.ServeMux, 27 | endpoint string, 28 | opts []grpc.DialOption) (err error) 29 | 30 | var endpointFuncs = []RegisterHandlerFromEndpoint{ 31 | ping_pb.RegisterPingSvcHandlerFromEndpoint, 32 | pet_pb.RegisterPetStoreSvcHandlerFromEndpoint, 33 | } 34 | 35 | // RegisterServers register grpc services. 36 | func RegisterServers(grpcServer *grpc.Server, logger *logrus.Logger, db *ent.Client, tracer trace.Tracer) { 37 | ping_pb.RegisterPingSvcServer(grpcServer, ping_svc.New(logger, tracer)) 38 | pet_pb.RegisterPetStoreSvcServer(grpcServer, pet_svc.New(logger, db)) 39 | } 40 | 41 | // RegisterHandlers register grpc gateway handlers. 42 | func RegisterHandlers(gwmux *runtime.ServeMux, grpcAddr string) { 43 | ctx := context.Background() 44 | dopts := []grpc.DialOption{ 45 | grpc.WithInsecure(), 46 | grpc.WithDefaultCallOptions( 47 | grpc.MaxCallRecvMsgSize(maxMsgSize), 48 | grpc.MaxCallSendMsgSize(maxMsgSize), 49 | ), 50 | } 51 | for _, f := range endpointFuncs { 52 | f(ctx, gwmux, grpcAddr, dopts) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "log" 5 | "reflect" 6 | "strings" 7 | 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | type Config struct { 12 | Server ServerConfig `mapstructure:"server"` 13 | Database DatabaseConfig `mapstructure:"database"` 14 | Log LogConfig `mapstructure:"log"` 15 | Daemon DaemonConfig `mapstructure:"daemon"` 16 | } 17 | 18 | func LoadConfig(configPath string) *Config { 19 | c, err := loadConfig(configPath) 20 | if err != nil { 21 | log.Println("warning, config file not found, use default config", err) 22 | } 23 | return c 24 | } 25 | 26 | func loadConfig(configPath string) (*Config, error) { 27 | viper.SetConfigFile(configPath) 28 | viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 29 | viper.SetEnvPrefix("EASYCODING") 30 | viper.AutomaticEnv() 31 | 32 | err := viper.ReadInConfig() 33 | if err != nil { 34 | return nil, err 35 | } 36 | c := Config{} 37 | bindValues(c) 38 | viper.Unmarshal(&c) 39 | 40 | viper.WatchConfig() 41 | return &c, nil 42 | } 43 | 44 | func bindValues(iface interface{}, parts ...string) { 45 | ifv := reflect.ValueOf(iface) 46 | ift := reflect.TypeOf(iface) 47 | for i := 0; i < ift.NumField(); i++ { 48 | v := ifv.Field(i) 49 | t := ift.Field(i) 50 | tv, ok := t.Tag.Lookup("mapstructure") 51 | if !ok { 52 | continue 53 | } 54 | switch v.Kind() { 55 | case reflect.Struct: 56 | bindValues(v.Interface(), append(parts, tv)...) 57 | default: 58 | key := strings.Join(append(parts, tv), ".") 59 | viper.BindEnv(key) 60 | dv, ok := t.Tag.Lookup("defaultvalue") 61 | if !ok { 62 | continue 63 | } 64 | viper.SetDefault(key, dv) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /cmd/migrate/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | c "easycoding/internal/config" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/golang-migrate/migrate/v4" 9 | "github.com/sirupsen/logrus" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | const ( 14 | defaultMigrationDir = "./migrations" 15 | ) 16 | 17 | var ( 18 | dryrun bool 19 | migrationDir string 20 | migrateAll bool 21 | configPath string 22 | 23 | config *c.Config 24 | ) 25 | 26 | var rootCmd = &cobra.Command{ 27 | Use: "migrate", 28 | Short: "migrate database", 29 | Run: func(cmd *cobra.Command, args []string) { 30 | err := cmd.Help() 31 | if err != nil { 32 | fmt.Println(err) 33 | } 34 | os.Exit(1) 35 | }, 36 | } 37 | 38 | type migrateLogger struct { 39 | logger *logrus.Logger 40 | } 41 | 42 | func newMigrateLogger() migrate.Logger { 43 | return &migrateLogger{ 44 | logger: logrus.New(), 45 | } 46 | } 47 | 48 | var _ migrate.Logger = (*migrateLogger)(nil) 49 | 50 | func (logger *migrateLogger) Printf(format string, v ...interface{}) { 51 | logger.logger.Infof(format, v...) 52 | } 53 | 54 | func (logger *migrateLogger) Verbose() bool { 55 | return true 56 | } 57 | 58 | func InitCmd() error { 59 | rootCmd.PersistentFlags().BoolVarP(&dryrun, "dry-run", "n", false, "--dry-run") 60 | rootCmd.PersistentFlags().BoolVar(&migrateAll, "all", false, "--all") 61 | rootCmd.PersistentFlags().StringVarP( 62 | &migrationDir, "migrate-path", "p", defaultMigrationDir, "--migrate-path") 63 | rootCmd.PersistentFlags().StringVar( 64 | &configPath, "config", "./config.yaml", "--config") 65 | config = c.LoadConfig(configPath) 66 | initStep() 67 | initDiff() 68 | initTo() 69 | return initVersion() 70 | } 71 | 72 | func Execute() error { 73 | return rootCmd.Execute() 74 | } 75 | -------------------------------------------------------------------------------- /pkg/db/migration_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "easycoding/common/workspace" 5 | "fmt" 6 | "path" 7 | "testing" 8 | ) 9 | 10 | type testData struct { 11 | From int 12 | To int 13 | ExpectDirection MigrationDirection 14 | ExceptStep int 15 | ExceptErr error 16 | } 17 | 18 | var dirname = path.Join(workspace.GetWorkspace(), "pkg", "db", "test", "migrations") 19 | 20 | func TestMigration(t *testing.T) { 21 | dataList := []testData{ 22 | { 23 | From: 20220914020936, 24 | To: 20220914055720, 25 | ExpectDirection: MigrationDirectionUP, 26 | ExceptStep: 1, 27 | ExceptErr: nil, 28 | }, 29 | { 30 | From: 20220914020936, 31 | To: 20221120094056, 32 | ExpectDirection: MigrationDirectionUP, 33 | ExceptStep: 4, 34 | ExceptErr: nil, 35 | }, 36 | { 37 | From: 20221120094056, 38 | To: 20220914020936, 39 | ExpectDirection: MigrationDirectionDown, 40 | ExceptStep: 4, 41 | ExceptErr: nil, 42 | }, 43 | } 44 | for _, data := range dataList { 45 | t.Run(fmt.Sprintf("from: %v, to: %v", data.From, data.To), func(t *testing.T) { 46 | d, step, err := MigrationGenerate(dirname, data.From, data.To) 47 | if d != data.ExpectDirection { 48 | t.Errorf("from: %v, to: %v, error direction %s, expected %s", data.From, data.To, d, data.ExpectDirection) 49 | } 50 | if step != data.ExceptStep { 51 | t.Errorf("from: %v, to: %v, error step %v, expected %v", data.From, data.To, step, data.ExceptStep) 52 | } 53 | if err != data.ExceptErr { 54 | t.Errorf("from: %v, to: %v, error %v, expected %v", data.From, data.To, err, data.ExceptErr) 55 | } 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /api/third_party_apis/google/type/money.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.type; 18 | 19 | option cc_enable_arenas = true; 20 | option go_package = "google.golang.org/genproto/googleapis/type/money;money"; 21 | option java_multiple_files = true; 22 | option java_outer_classname = "MoneyProto"; 23 | option java_package = "com.google.type"; 24 | option objc_class_prefix = "GTP"; 25 | 26 | // Represents an amount of money with its currency type. 27 | message Money { 28 | // The three-letter currency code defined in ISO 4217. 29 | string currency_code = 1; 30 | 31 | // The whole units of the amount. 32 | // For example if `currencyCode` is `"USD"`, then 1 unit is one US dollar. 33 | int64 units = 2; 34 | 35 | // Number of nano (10^-9) units of the amount. 36 | // The value must be between -999,999,999 and +999,999,999 inclusive. 37 | // If `units` is positive, `nanos` must be positive or zero. 38 | // If `units` is zero, `nanos` can be positive, zero, or negative. 39 | // If `units` is negative, `nanos` must be negative or zero. 40 | // For example $-1.75 is represented as `units`=-1 and `nanos`=-750,000,000. 41 | int32 nanos = 3; 42 | } 43 | -------------------------------------------------------------------------------- /pkg/otel/provider.go: -------------------------------------------------------------------------------- 1 | package otel 2 | 3 | import ( 4 | "context" 5 | 6 | "go.opentelemetry.io/otel" 7 | "go.opentelemetry.io/otel/exporters/jaeger" 8 | "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" 9 | "go.opentelemetry.io/otel/propagation" 10 | "go.opentelemetry.io/otel/sdk/resource" 11 | sdktrace "go.opentelemetry.io/otel/sdk/trace" 12 | semconv "go.opentelemetry.io/otel/semconv/v1.12.0" 13 | "go.opentelemetry.io/otel/trace" 14 | ) 15 | 16 | const ( 17 | traceName = "easycoding-trace" 18 | ) 19 | 20 | func NewTracer() (trace.Tracer, func() error, error) { 21 | exporter, err := newStdoutExporter(false) 22 | if err != nil { 23 | return nil, nil, err 24 | } 25 | tp := sdktrace.NewTracerProvider( 26 | sdktrace.WithSampler(sdktrace.AlwaysSample()), 27 | sdktrace.WithBatcher(exporter), 28 | sdktrace.WithResource(resource.NewWithAttributes( 29 | semconv.SchemaURL, 30 | semconv.ServiceNameKey.String("easycoding"), 31 | )), 32 | ) 33 | otel.SetTracerProvider(tp) 34 | otel.SetTextMapPropagator( 35 | propagation.NewCompositeTextMapPropagator( 36 | propagation.TraceContext{}, 37 | propagation.Baggage{}, 38 | ), 39 | ) 40 | tpShutdown := func() error { 41 | if err := tp.Shutdown(context.Background()); err != nil { 42 | return err 43 | } 44 | return nil 45 | } 46 | return tp.Tracer(traceName), tpShutdown, nil 47 | } 48 | 49 | func newStdoutExporter(pretty bool) (sdktrace.SpanExporter, error) { 50 | if pretty { 51 | return stdouttrace.New(stdouttrace.WithPrettyPrint()) 52 | } 53 | return stdouttrace.New() 54 | } 55 | 56 | func newJaegerExporter() (sdktrace.SpanExporter, error) { 57 | // TODO(qujiabao): mv the hard code trace address into config 58 | url := "http://localhost:14268/api/traces" 59 | return jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) 60 | } 61 | -------------------------------------------------------------------------------- /cmd/migrate/cmd/to.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "path/filepath" 8 | "strconv" 9 | 10 | "easycoding/common/workspace" 11 | pkg_db "easycoding/pkg/db" 12 | 13 | "github.com/golang-migrate/migrate/v4" 14 | "github.com/golang-migrate/migrate/v4/database/mysql" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | var toCmd = &cobra.Command{ 19 | Use: "to [version]", 20 | Short: "to specific version", 21 | RunE: func(cmd *cobra.Command, args []string) error { 22 | if len(args) != 1 { 23 | return errors.New("please input the target version") 24 | } 25 | toVersion, err := strconv.Atoi(args[0]) 26 | if err != nil { 27 | return errors.New("the version must be a number") 28 | } 29 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?multiStatements=true", 30 | config.Database.User, 31 | config.Database.Password, 32 | config.Database.Host, 33 | config.Database.Port, 34 | config.Database.DBName, 35 | ) 36 | db, err := sql.Open("mysql", dsn) 37 | if err != nil { 38 | return err 39 | } 40 | driver, err := mysql.WithInstance(db, &mysql.Config{}) 41 | if err != nil { 42 | return err 43 | } 44 | dir := filepath.Join(workspace.GetWorkspace(), migrationDir) 45 | fileUri := fmt.Sprintf("file://%s", dir) 46 | m, err := migrate.NewWithDatabaseInstance(fileUri, "mysql", driver) 47 | if err != nil { 48 | return err 49 | } 50 | currentVersion, dirty, err := m.Version() 51 | if err != nil { 52 | return err 53 | } 54 | if dirty { 55 | return errors.New("database is dirty, patch the diff first") 56 | } 57 | direction, step, err := pkg_db.MigrationGenerate(dir, int(currentVersion), toVersion) 58 | if err != nil { 59 | return err 60 | } 61 | if direction == pkg_db.MigrationDirectionUP { 62 | if err := m.Steps(step); err != nil { 63 | return err 64 | } 65 | } else { 66 | if err := m.Steps(step * -1); err != nil { 67 | return err 68 | } 69 | } 70 | return nil 71 | }, 72 | } 73 | 74 | func initTo() { 75 | rootCmd.AddCommand(toCmd) 76 | } 77 | -------------------------------------------------------------------------------- /pkg/db/migration.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | "regexp" 8 | "sort" 9 | "strconv" 10 | ) 11 | 12 | type MigrationDirection string 13 | 14 | const ( 15 | MigrationDirectionUP MigrationDirection = "up" 16 | MigrationDirectionDown MigrationDirection = "down" 17 | ) 18 | 19 | func MigrationGenerate(migrationDir string, from, to int) (MigrationDirection, int, error) { 20 | if from == to { 21 | return "", 0, errors.New("'from' equals to 'to'") 22 | } 23 | files, err := ioutil.ReadDir(migrationDir) 24 | if err != nil { 25 | return "", 0, err 26 | } 27 | migrationSteps := []int{} 28 | for _, f := range files { 29 | if f.IsDir() { 30 | continue 31 | } 32 | r := regexp.MustCompile(`^([0-9]{14})_changes\.([a-z]{2,4})\.sql$`) 33 | if !r.Match([]byte(f.Name())) { 34 | continue 35 | } 36 | matches := r.FindSubmatch([]byte(f.Name())) 37 | if len(matches) != 3 { 38 | return "", 0, errors.New(fmt.Sprintf("invalid match %s", f.Name())) 39 | } 40 | timestampStr := string(matches[1]) 41 | timestamp, err := strconv.Atoi(timestampStr) 42 | if err != nil { 43 | return "", 0, err 44 | } 45 | 46 | if !contains(migrationSteps, timestamp) { 47 | migrationSteps = append(migrationSteps, timestamp) 48 | } 49 | } 50 | sort.Slice(migrationSteps, func(i, j int) bool { return migrationSteps[i] < migrationSteps[j] }) 51 | fromIndex := indexOf(migrationSteps, from) 52 | toIndex := indexOf(migrationSteps, to) 53 | if fromIndex == -1 || toIndex == -1 { 54 | return "", 0, errors.New(fmt.Sprintf("error from/to name %v, %v", from, to)) 55 | } 56 | if fromIndex < toIndex { 57 | return MigrationDirectionUP, toIndex - fromIndex, nil 58 | } 59 | return MigrationDirectionDown, fromIndex - toIndex, nil 60 | } 61 | 62 | func contains(list []int, i int) bool { 63 | for _, l := range list { 64 | if l == i { 65 | return true 66 | } 67 | } 68 | return false 69 | } 70 | 71 | func indexOf(list []int, i int) int { 72 | for index, l := range list { 73 | if l == i { 74 | return index 75 | } 76 | } 77 | return -1 78 | } 79 | -------------------------------------------------------------------------------- /pkg/db/ent.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "easycoding/internal/config" 7 | "easycoding/pkg/ent" 8 | "easycoding/pkg/errors" 9 | "fmt" 10 | 11 | _ "github.com/go-sql-driver/mysql" 12 | "go.opentelemetry.io/otel/attribute" 13 | "go.opentelemetry.io/otel/trace" 14 | 15 | entsql "entgo.io/ent/dialect/sql" 16 | ) 17 | 18 | func CreateDBClient(config *config.Config, tracer trace.Tracer) (*ent.Client, error) { 19 | if config.Database.CreateDatabase { 20 | if err := CreateDatabase(config); err != nil { 21 | return nil, err 22 | } 23 | } 24 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&charset=utf8", 25 | config.Database.User, 26 | config.Database.Password, 27 | config.Database.Host, 28 | config.Database.Port, 29 | config.Database.DBName, 30 | ) 31 | db, err := sql.Open("mysql", dsn) 32 | if err != nil { 33 | return nil, err 34 | } 35 | drv := entsql.OpenDB("mysql", db) 36 | 37 | client := ent.NewClient(ent.Driver(drv)) 38 | client.Use(func(next ent.Mutator) ent.Mutator { 39 | return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) { 40 | opType := m.Op().String() 41 | tablesName := m.Type() 42 | spanName := fmt.Sprintf("%s-%s", tablesName, opType) 43 | newCtx, span := tracer.Start(ctx, spanName) 44 | span.SetAttributes( 45 | attribute.String("table", tablesName), 46 | attribute.String("operation", opType), 47 | ) 48 | defer span.End() 49 | return next.Mutate(newCtx, m) 50 | }) 51 | }) 52 | return client, nil 53 | } 54 | 55 | func CreateDatabase(config *config.Config) error { 56 | db, err := sql.Open("mysql", fmt.Sprintf( 57 | "%s:%s@tcp(%s:%s)/", 58 | config.Database.User, 59 | config.Database.Password, 60 | config.Database.Host, 61 | config.Database.Port, 62 | )) 63 | if err != nil { 64 | return errors.ErrInvalidf(err, "fail to open connection with mysql") 65 | } 66 | defer db.Close() 67 | 68 | _, err = db.Exec(fmt.Sprintf( 69 | "CREATE DATABASE IF NOT EXISTS %s ", config.Database.DBName)) 70 | if err != nil { 71 | return errors.ErrInvalid(err) 72 | } 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /pkg/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "os" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | // New returns a logger implemented using the logrus package. 12 | func New(wr io.Writer, level, dir string) *logrus.Logger { 13 | if wr == nil { 14 | wr = os.Stderr 15 | } 16 | 17 | lr := logrus.New() 18 | lr.Out = wr 19 | 20 | lvl, err := logrus.ParseLevel(level) 21 | if err != nil { 22 | lvl = logrus.WarnLevel 23 | lr.Warnf("failed to parse log-level '%s', defaulting to 'warning'", level) 24 | } 25 | lr.SetLevel(lvl) 26 | lr.SetFormatter(getFormatter(false)) 27 | 28 | if dir != "" { 29 | _ = ensureDir(dir) 30 | // nolint:gocritic 31 | fileHook, err := NewLogrusFileHook(dir+"/app.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666) 32 | if err == nil { 33 | lr.Hooks.Add(fileHook) 34 | } else { 35 | lr.Warnf("Failed to open logfile, using standard out: %v", err) 36 | } 37 | } 38 | 39 | return lr 40 | } 41 | 42 | func NewNullLogger() *logrus.Logger { 43 | lr := logrus.New() 44 | lr.SetOutput(ioutil.Discard) 45 | return lr 46 | } 47 | 48 | func NewFromWriter(w io.Writer) *logrus.Logger { 49 | lr := logrus.New() 50 | lr.SetOutput(w) 51 | lr.SetLevel(logrus.DebugLevel) 52 | return lr 53 | } 54 | 55 | // getFormatter returns the default log formatter. 56 | func getFormatter(disableColors bool) *textFormatter { 57 | return &textFormatter{ 58 | DisableColors: disableColors, 59 | ForceFormatting: true, 60 | ForceColors: true, 61 | DisableTimestamp: false, 62 | FullTimestamp: true, 63 | DisableSorting: true, 64 | TimestampFormat: "2006-01-02 15:04:05.000000", 65 | SpacePadding: 45, 66 | } 67 | } 68 | 69 | func ensureDir(path string) error { 70 | isExisting, err := exists(path) 71 | if err != nil { 72 | return err 73 | } 74 | if !isExisting { 75 | return os.MkdirAll(path, os.ModePerm) 76 | } 77 | return nil 78 | } 79 | 80 | func exists(path string) (bool, error) { 81 | _, err := os.Stat(path) 82 | if err == nil { 83 | return true, nil 84 | } 85 | if os.IsNotExist(err) { 86 | return false, nil 87 | } 88 | return true, err 89 | } 90 | -------------------------------------------------------------------------------- /pkg/ent/enttest/enttest.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package enttest 4 | 5 | import ( 6 | "context" 7 | "easycoding/pkg/ent" 8 | // required by schema hooks. 9 | _ "easycoding/pkg/ent/runtime" 10 | 11 | "easycoding/pkg/ent/migrate" 12 | 13 | "entgo.io/ent/dialect/sql/schema" 14 | ) 15 | 16 | type ( 17 | // TestingT is the interface that is shared between 18 | // testing.T and testing.B and used by enttest. 19 | TestingT interface { 20 | FailNow() 21 | Error(...interface{}) 22 | } 23 | 24 | // Option configures client creation. 25 | Option func(*options) 26 | 27 | options struct { 28 | opts []ent.Option 29 | migrateOpts []schema.MigrateOption 30 | } 31 | ) 32 | 33 | // WithOptions forwards options to client creation. 34 | func WithOptions(opts ...ent.Option) Option { 35 | return func(o *options) { 36 | o.opts = append(o.opts, opts...) 37 | } 38 | } 39 | 40 | // WithMigrateOptions forwards options to auto migration. 41 | func WithMigrateOptions(opts ...schema.MigrateOption) Option { 42 | return func(o *options) { 43 | o.migrateOpts = append(o.migrateOpts, opts...) 44 | } 45 | } 46 | 47 | func newOptions(opts []Option) *options { 48 | o := &options{} 49 | for _, opt := range opts { 50 | opt(o) 51 | } 52 | return o 53 | } 54 | 55 | // Open calls ent.Open and auto-run migration. 56 | func Open(t TestingT, driverName, dataSourceName string, opts ...Option) *ent.Client { 57 | o := newOptions(opts) 58 | c, err := ent.Open(driverName, dataSourceName, o.opts...) 59 | if err != nil { 60 | t.Error(err) 61 | t.FailNow() 62 | } 63 | migrateSchema(t, c, o) 64 | return c 65 | } 66 | 67 | // NewClient calls ent.NewClient and auto-run migration. 68 | func NewClient(t TestingT, opts ...Option) *ent.Client { 69 | o := newOptions(opts) 70 | c := ent.NewClient(o.opts...) 71 | migrateSchema(t, c, o) 72 | return c 73 | } 74 | func migrateSchema(t TestingT, c *ent.Client, o *options) { 75 | tables, err := schema.CopyTables(migrate.Tables) 76 | if err != nil { 77 | t.Error(err) 78 | t.FailNow() 79 | } 80 | if err := migrate.Create(context.Background(), c.Schema, tables, o.migrateOpts...); err != nil { 81 | t.Error(err) 82 | t.FailNow() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /cmd/manage/cmd/proto/swagger.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "easycoding/common/exec" 5 | "easycoding/common/workspace" 6 | "errors" 7 | "fmt" 8 | "io/fs" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | const ( 16 | excludeDir = "google" 17 | outputName = "api.swagger.json" 18 | ) 19 | 20 | var generateCmd = &cobra.Command{ 21 | Use: "gen-swagger", 22 | Short: "generate the api/api.swagger.json files", 23 | Run: func(cmd *cobra.Command, args []string) { 24 | dryrun := getDryrun(cmd) 25 | commands := []string{"swagger", "mixin"} 26 | 27 | swaggerFiles := getSwaggerJsonFiles() 28 | commands = append(commands, swaggerFiles...) 29 | commands = append(commands, 30 | "--output", filepath.Join(workspace.GetWorkspace(), apiDir, outputName)) 31 | commands = append(commands, "--quiet") 32 | cwd := filepath.Join(workspace.GetWorkspace(), apiDir) 33 | exec.ExecCommand(commands, cwd, dryrun) 34 | }, 35 | } 36 | 37 | var cleanCmd = &cobra.Command{ 38 | Use: "clean-swagger", 39 | Short: "delete the api/api.swagger.json files", 40 | Run: func(cmd *cobra.Command, args []string) { 41 | dryrun := getDryrun(cmd) 42 | swaggerJson := filepath.Join(workspace.GetWorkspace(), apiDir, outputName) 43 | commands := []string{"rm", swaggerJson} 44 | cwd := filepath.Join(workspace.GetWorkspace(), apiDir) 45 | err := exec.ExecCommand(commands, cwd, dryrun) 46 | if err != nil { 47 | fmt.Println(err) 48 | } 49 | }, 50 | } 51 | 52 | func initSwagger() { 53 | protoCmd.AddCommand(generateCmd) 54 | protoCmd.AddCommand(cleanCmd) 55 | } 56 | 57 | func getSwaggerJsonFiles() []string { 58 | root := filepath.Join(workspace.GetWorkspace(), apiDir) 59 | files := []string{} 60 | filepath.Walk(root, func(path string, info fs.FileInfo, err error) error { 61 | if err != nil { 62 | if errors.Is(err, filepath.SkipDir) { 63 | return nil 64 | } 65 | return err 66 | } 67 | if info.IsDir() && info.Name() == excludeDir { 68 | return filepath.SkipDir 69 | } 70 | 71 | if info.IsDir() { 72 | return nil 73 | } 74 | 75 | if info.Name() == outputName { 76 | return nil 77 | } 78 | if strings.HasSuffix(info.Name(), ".swagger.json") { 79 | files = append(files, path) 80 | } 81 | return nil 82 | }) 83 | return files 84 | } 85 | -------------------------------------------------------------------------------- /cmd/manage/cmd/db_migrate/generate.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "easycoding/common/workspace" 6 | "easycoding/pkg/db" 7 | "easycoding/pkg/ent/migrate" 8 | "errors" 9 | "fmt" 10 | "log" 11 | "os" 12 | "path/filepath" 13 | 14 | "ariga.io/atlas/sql/sqltool" 15 | "entgo.io/ent/dialect" 16 | "entgo.io/ent/dialect/sql/schema" 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | var generateCmd = &cobra.Command{ 21 | Use: "generate", 22 | Short: "Generate migration files", 23 | RunE: func(cmd *cobra.Command, args []string) error { 24 | if config.Database.CreateDatabase { 25 | if err := db.CreateDatabase(config); err != nil { 26 | return err 27 | } 28 | } 29 | migrationDir := filepath.Join(workspace.GetWorkspace(), "migrations") 30 | if _, err := os.Stat(migrationDir); errors.Is(err, os.ErrNotExist) { 31 | err := os.Mkdir(migrationDir, os.ModePerm) 32 | if err != nil { 33 | return err 34 | } 35 | } 36 | ctx := context.Background() 37 | // Create a local migration directory able to understand golang-migrate 38 | // migration file format for replay. 39 | dir, err := sqltool.NewGolangMigrateDir( 40 | filepath.Join(workspace.GetWorkspace(), "migrations")) 41 | if err != nil { 42 | log.Fatalf("failed creating atlas migration directory: %v", err) 43 | } 44 | // Migrate diff options. 45 | opts := []schema.MigrateOption{ 46 | schema.WithDir(dir), // provide migration directory 47 | schema.WithMigrationMode(schema.ModeReplay), // provide migration mode 48 | schema.WithDialect(dialect.MySQL), // Ent dialect to use 49 | schema.WithFormatter(sqltool.GolangMigrateFormatter), 50 | schema.WithDropColumn(true), 51 | schema.WithDropIndex(true), 52 | } 53 | // Generate migrations using Atlas support for MySQL (note the Ent 54 | // dialect option passed above). 55 | dsn := fmt.Sprintf("mysql://%s:%s@%s:%s/%s", 56 | config.Database.User, 57 | config.Database.Password, 58 | config.Database.Host, 59 | config.Database.Port, 60 | config.Database.DBName, 61 | ) 62 | err = migrate.Diff(ctx, dsn, opts...) 63 | if err != nil { 64 | log.Fatalf("failed generating migration file: %v", err) 65 | } 66 | return nil 67 | 68 | }, 69 | } 70 | 71 | func initGenerate() { 72 | dbMigrateCmd.AddCommand(generateCmd) 73 | } 74 | -------------------------------------------------------------------------------- /internal/daemon/daemon.go: -------------------------------------------------------------------------------- 1 | package daemon 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | // Daemon defines all required methods of a background daemon process. 12 | type Daemon interface { 13 | Name() string 14 | Run(ctx context.Context, wg *sync.WaitGroup, logger *logrus.Logger) error 15 | } 16 | 17 | // Manager takes care of starting and orchestrating daemon processes. 18 | type Manager struct { 19 | log *logrus.Logger 20 | // wg is used to keep track of running daemons. 21 | wg *sync.WaitGroup 22 | // ctx is used to signal cancellation to running daemons. 23 | ctx context.Context 24 | } 25 | 26 | // NewManager returns a new manager instance. 27 | func NewManager( 28 | ctx context.Context, 29 | wg *sync.WaitGroup, 30 | l *logrus.Logger, 31 | ) *Manager { 32 | return &Manager{ 33 | ctx: ctx, 34 | wg: wg, 35 | log: l, 36 | } 37 | } 38 | 39 | // Start starts a daemon. 40 | func (m *Manager) Start(d Daemon) { 41 | m.wg.Add(1) 42 | defer m.wg.Done() 43 | 44 | var wg sync.WaitGroup 45 | var try int 46 | 47 | logger := m.log 48 | go m.recoverPanic(d, m.log) 49 | 50 | for { 51 | select { 52 | // If the daemon should stop, wait for the wg to be done, then 53 | // stop the restart ticket and exit the method. 54 | case <-m.ctx.Done(): 55 | wg.Wait() 56 | logger.Infof(`daemon "%s" shutdown complete`, d.Name()) 57 | return 58 | default: 59 | try++ 60 | logger.Tracef(`starting daemon "%s" (%d. try)...`, d.Name(), try) 61 | // Run the daemon. If it crashes, continue to the next ticker iteration. 62 | if err := d.Run(m.ctx, &wg, logger); err != nil { 63 | logger.Warnf(`daemon "%s" crashed: %s`, d.Name(), err) 64 | continue 65 | } 66 | // If the Run method returned without an error, reset the try counter 67 | // and restart the daemon again. All daemons are run forever, even 68 | // if the return without an error. 69 | logger.Debugf(`daemon "%s" exited without errors`, d.Name()) 70 | try = 0 71 | } 72 | } 73 | } 74 | 75 | // recoverPanic recovers a crashed daemon and restarts it. 76 | func (m *Manager) recoverPanic(d Daemon, logger *logrus.Logger) { 77 | if err := recover(); err != nil { 78 | logger.Errorf("daemon exited with panic (restarting in 5 seconds): %s", err) 79 | time.Sleep(5 * time.Second) 80 | m.Start(d) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /cmd/migrate/cmd/step.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "database/sql" 5 | "easycoding/common/workspace" 6 | "errors" 7 | "fmt" 8 | "path/filepath" 9 | "strconv" 10 | 11 | "github.com/golang-migrate/migrate/v4" 12 | "github.com/golang-migrate/migrate/v4/database/mysql" 13 | _ "github.com/golang-migrate/migrate/v4/source/file" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | const ( 18 | maxStep = 1000 19 | ) 20 | 21 | var stepLatest bool 22 | var stepReverse bool 23 | 24 | var stepCmd = &cobra.Command{ 25 | Use: "step [step_number]", 26 | Short: "Migrate step", 27 | RunE: func(cmd *cobra.Command, args []string) error { 28 | step := 0 29 | if len(args) != 1 && !stepLatest { 30 | return errors.New("input the step number or use --latest") 31 | } 32 | if !stepLatest { 33 | var err error 34 | step, err = strconv.Atoi(args[0]) 35 | if err != nil { 36 | return fmt.Errorf("step invalid %s, %s", args[0], err.Error()) 37 | } 38 | if step > maxStep { 39 | return fmt.Errorf("max step %v", maxStep) 40 | } 41 | } 42 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?multiStatements=true", 43 | config.Database.User, 44 | config.Database.Password, 45 | config.Database.Host, 46 | config.Database.Port, 47 | config.Database.DBName, 48 | ) 49 | db, err := sql.Open("mysql", dsn) 50 | if err != nil { 51 | return err 52 | } 53 | driver, err := mysql.WithInstance(db, &mysql.Config{}) 54 | if err != nil { 55 | return err 56 | } 57 | dir := filepath.Join(workspace.GetWorkspace(), migrationDir) 58 | fileUri := fmt.Sprintf("file://%s", dir) 59 | m, err := migrate.NewWithDatabaseInstance(fileUri, "mysql", driver) 60 | m.Log = newMigrateLogger() 61 | if err != nil { 62 | return err 63 | } 64 | if stepLatest { 65 | if !stepReverse { 66 | if err := m.Up(); err != nil { 67 | return err 68 | } 69 | } else { 70 | if err := m.Down(); err != nil { 71 | return err 72 | } 73 | } 74 | } else { 75 | if !stepReverse { 76 | if err := m.Steps(step); err != nil { 77 | return err 78 | } 79 | } else { 80 | if err := m.Steps(step * -1); err != nil { 81 | return err 82 | } 83 | } 84 | } 85 | return nil 86 | }, 87 | } 88 | 89 | func initStep() { 90 | rootCmd.AddCommand(stepCmd) 91 | stepCmd.Flags().BoolVar(&stepLatest, "latest", false, "--latest") 92 | stepCmd.Flags().BoolVar(&stepReverse, "reverse", false, "--reverse") 93 | } 94 | -------------------------------------------------------------------------------- /cmd/manage/cmd/proto/go.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strings" 7 | 8 | "github.com/spf13/cobra" 9 | "gopkg.in/yaml.v3" 10 | 11 | "easycoding/common/exec" 12 | "easycoding/common/workspace" 13 | ) 14 | 15 | const ( 16 | apiDir = "api" 17 | bufWorkFile = "buf.work.yaml" 18 | apiSubfix = "_apis" 19 | ignoreModels = ".buf.ignore.yaml" 20 | ) 21 | 22 | // The schema to buf.work.yaml 23 | type bufWork struct { 24 | Directories []string 25 | } 26 | 27 | // The schema to .buf.ignore.yaml 28 | type bufIgnore struct { 29 | IgnoreModels []string `yaml:"ignore_modules"` 30 | } 31 | 32 | var genGoCmd = &cobra.Command{ 33 | Use: "gen-go", 34 | Short: "Generate golang protobuf, grpc, grpc gateway, swagger json files", 35 | RunE: func(cmd *cobra.Command, args []string) error { 36 | dryrun := getDryrun(cmd) 37 | commands := []string{"buf", "generate"} 38 | cwd := filepath.Join(workspace.GetWorkspace(), apiDir) 39 | err := exec.ExecCommand(commands, cwd, dryrun) 40 | if err != nil { 41 | return err 42 | } 43 | delDirNames, err := getIgnoreDirs() 44 | if err != nil { 45 | return err 46 | } 47 | rmCommands := []string{"rm", "-r"} 48 | rmCommands = append(rmCommands, delDirNames...) 49 | 50 | return exec.ExecCommand(rmCommands, cwd, dryrun) 51 | }, 52 | } 53 | 54 | var cleanGoCmd = &cobra.Command{ 55 | Use: "clean-go", 56 | Short: "Clean generate files", 57 | RunE: func(cmd *cobra.Command, args []string) error { 58 | dryrun := getDryrun(cmd) 59 | c, err := os.ReadFile(filepath.Join(workspace.GetWorkspace(), apiDir, bufWorkFile)) 60 | if err != nil { 61 | return err 62 | } 63 | bw := bufWork{} 64 | if err := yaml.Unmarshal(c, &bw); err != nil { 65 | return err 66 | } 67 | delDirNames := []string{} 68 | for _, d := range bw.Directories { 69 | delDirNames = append(delDirNames, strings.TrimSuffix(d, apiSubfix)) 70 | } 71 | commands := []string{"rm", "-r"} 72 | commands = append(commands, delDirNames...) 73 | cwd := filepath.Join(workspace.GetWorkspace(), apiDir) 74 | exec.ExecCommand(commands, cwd, dryrun) 75 | return nil 76 | }, 77 | } 78 | 79 | func initGo() { 80 | protoCmd.AddCommand(genGoCmd) 81 | protoCmd.AddCommand(cleanGoCmd) 82 | } 83 | 84 | func getIgnoreDirs() ([]string, error) { 85 | c, err := os.ReadFile(filepath.Join(workspace.GetWorkspace(), apiDir, ignoreModels)) 86 | if err != nil { 87 | return nil, err 88 | } 89 | bw := bufIgnore{} 90 | if err := yaml.Unmarshal(c, &bw); err != nil { 91 | return nil, err 92 | } 93 | return bw.IgnoreModels, nil 94 | } 95 | -------------------------------------------------------------------------------- /cmd/migrate/cmd/diff.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | 11 | "easycoding/common/workspace" 12 | "easycoding/pkg/ent" 13 | 14 | "entgo.io/ent/dialect/sql/schema" 15 | "github.com/golang-migrate/migrate/v4" 16 | "github.com/golang-migrate/migrate/v4/database/mysql" 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | var applyDiffToDB bool 21 | 22 | var diffCmd = &cobra.Command{ 23 | Use: "diff", 24 | Short: "Show diff between schema and db", 25 | RunE: func(cmd *cobra.Command, args []string) error { 26 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?multiStatements=true", 27 | config.Database.User, 28 | config.Database.Password, 29 | config.Database.Host, 30 | config.Database.Port, 31 | config.Database.DBName, 32 | ) 33 | client, err := ent.Open("mysql", dsn) 34 | if err != nil { 35 | return err 36 | } 37 | defer client.Close() 38 | migrateOptions := []schema.MigrateOption{ 39 | schema.WithDropColumn(true), 40 | schema.WithDropIndex(true), 41 | } 42 | if err := printDiff(client, migrateOptions); err != nil { 43 | log.Fatalf("failed printing schema changes: %v", err) 44 | return err 45 | } 46 | if applyDiffToDB { 47 | if err := applyDiff(client, migrateOptions); err != nil { 48 | log.Fatalf("failed apply diff: %v", err) 49 | return err 50 | } 51 | if err := dirtyToFalse(dsn); err != nil { 52 | log.Fatalf("failed change dirty to false: %v", err) 53 | return err 54 | } 55 | log.Println("apply success") 56 | } 57 | return nil 58 | }, 59 | } 60 | 61 | func printDiff(client *ent.Client, migrateOptions []schema.MigrateOption) error { 62 | if err := client.Schema.WriteTo( 63 | context.Background(), 64 | os.Stdout, 65 | migrateOptions..., 66 | ); err != nil { 67 | return err 68 | } 69 | return nil 70 | } 71 | 72 | func applyDiff(client *ent.Client, migrateOptions []schema.MigrateOption) error { 73 | if err := client.Schema.Create( 74 | context.Background(), 75 | migrateOptions..., 76 | ); err != nil { 77 | return err 78 | } 79 | return nil 80 | } 81 | 82 | func dirtyToFalse(dsn string) error { 83 | dir := filepath.Join(workspace.GetWorkspace(), migrationDir) 84 | fileUri := fmt.Sprintf("file://%s", dir) 85 | db, err := sql.Open("mysql", dsn) 86 | if err != nil { 87 | return err 88 | } 89 | driver, err := mysql.WithInstance(db, &mysql.Config{}) 90 | if err != nil { 91 | return err 92 | } 93 | m, err := migrate.NewWithDatabaseInstance(fileUri, "mysql", driver) 94 | m.Log = newMigrateLogger() 95 | if err != nil { 96 | return err 97 | } 98 | v, _, _ := m.Version() 99 | if err := m.Force(int(v)); err != nil { 100 | return err 101 | } 102 | return nil 103 | } 104 | 105 | func initDiff() { 106 | rootCmd.AddCommand(diffCmd) 107 | diffCmd.Flags().BoolVar(&applyDiffToDB, "apply", false, "--apply") 108 | } 109 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Configuration of git pre-commit hooks: 4 | # https://pre-commit.com/ 5 | # 6 | # In a newly cloned git repo, run the following commands to install the 7 | # pre-commit hooks into .git/hooks/pre-commit: 8 | # pip3 install pre-commit 9 | # pre-commit install 10 | # 11 | # After that, `git commit` will automatically run the pre-commit hooks. 12 | # 13 | # Run all checks: 14 | # pre-commit run --all 15 | # 16 | # Run trailing-whitespace check for common/workspace/*.go: 17 | # pre-commit run trailing-whitespace --files common/workspace/*.go 18 | # 19 | # Run all checks for common/workspace/*.go:: 20 | # pre-commit run --files common/workspace/*.go 21 | 22 | 23 | # Skip pre-commit hooks: 24 | # 25 | # In some situations, for example, when fixing legacy codes, it is desirable to 26 | # skip pre-commit hooks. You can do so via the -n option of git-commit, as the 27 | # follows: 28 | # git commit -n 29 | # However, please DO NOT use this unless you absolutely have to. 30 | 31 | exclude: > 32 | (?x)^( 33 | .*\.pb\.go| # Protobuf files 34 | .*\.pb\.gw\.go| # Protobuf gateway files 35 | .*\.pb\.validate\.go| # Protobuf validate files 36 | .*\.swagger\.json| # Swagger json files 37 | )$ 38 | 39 | repos: 40 | - repo: https://github.com/pre-commit/pre-commit-hooks 41 | rev: v4.3.0 42 | hooks: 43 | - id: trailing-whitespace 44 | stages: 45 | - commit 46 | - id: check-added-large-files 47 | stages: 48 | - commit 49 | - id: check-case-conflict 50 | stages: 51 | - commit 52 | - id: check-docstring-first 53 | stages: 54 | - commit 55 | - id: check-executables-have-shebangs 56 | stages: 57 | - commit 58 | - id: check-json 59 | stages: 60 | - commit 61 | - id: check-symlinks 62 | stages: 63 | - commit 64 | - id: check-yaml 65 | stages: 66 | - commit 67 | - id: double-quote-string-fixer 68 | stages: 69 | - commit 70 | - id: end-of-file-fixer 71 | stages: 72 | - commit 73 | - id: mixed-line-ending 74 | stages: 75 | - commit 76 | - id: pretty-format-json 77 | stages: 78 | - commit 79 | args: 80 | - --autofix 81 | - --indent=4 82 | - id: sort-simple-yaml 83 | stages: 84 | - commit 85 | 86 | - repo: local 87 | # golangci-lint does not support {entry} file1 file2 ... which 88 | # the file1 and file2 in different directory 89 | hooks: 90 | - id: golint 91 | entry: go run cmd/lint/main.go go 92 | language: system 93 | name: lint .go source files 94 | files: ^.*\.go$ 95 | stages: 96 | - commit 97 | # buf lint does not support {entry} file1 file2 ... 98 | - id: buflint 99 | entry: go run cmd/lint/main.go proto 100 | language: system 101 | name: lint .proto source files 102 | files: ^.*\.proto$ 103 | stages: 104 | - commit 105 | -------------------------------------------------------------------------------- /pkg/ent/user.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "easycoding/pkg/ent/user" 7 | "fmt" 8 | "strings" 9 | 10 | "entgo.io/ent/dialect/sql" 11 | ) 12 | 13 | // User is the model entity for the User schema. 14 | type User struct { 15 | config `json:"-"` 16 | // ID of the ent. 17 | ID int `json:"id,omitempty"` 18 | // Name holds the value of the "name" field. 19 | Name string `json:"name,omitempty"` 20 | } 21 | 22 | // scanValues returns the types for scanning values from sql.Rows. 23 | func (*User) scanValues(columns []string) ([]interface{}, error) { 24 | values := make([]interface{}, len(columns)) 25 | for i := range columns { 26 | switch columns[i] { 27 | case user.FieldID: 28 | values[i] = new(sql.NullInt64) 29 | case user.FieldName: 30 | values[i] = new(sql.NullString) 31 | default: 32 | return nil, fmt.Errorf("unexpected column %q for type User", columns[i]) 33 | } 34 | } 35 | return values, nil 36 | } 37 | 38 | // assignValues assigns the values that were returned from sql.Rows (after scanning) 39 | // to the User fields. 40 | func (u *User) assignValues(columns []string, values []interface{}) error { 41 | if m, n := len(values), len(columns); m < n { 42 | return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) 43 | } 44 | for i := range columns { 45 | switch columns[i] { 46 | case user.FieldID: 47 | value, ok := values[i].(*sql.NullInt64) 48 | if !ok { 49 | return fmt.Errorf("unexpected type %T for field id", value) 50 | } 51 | u.ID = int(value.Int64) 52 | case user.FieldName: 53 | if value, ok := values[i].(*sql.NullString); !ok { 54 | return fmt.Errorf("unexpected type %T for field name", values[i]) 55 | } else if value.Valid { 56 | u.Name = value.String 57 | } 58 | } 59 | } 60 | return nil 61 | } 62 | 63 | // Update returns a builder for updating this User. 64 | // Note that you need to call User.Unwrap() before calling this method if this User 65 | // was returned from a transaction, and the transaction was committed or rolled back. 66 | func (u *User) Update() *UserUpdateOne { 67 | return (&UserClient{config: u.config}).UpdateOne(u) 68 | } 69 | 70 | // Unwrap unwraps the User entity that was returned from a transaction after it was closed, 71 | // so that all future queries will be executed through the driver which created the transaction. 72 | func (u *User) Unwrap() *User { 73 | _tx, ok := u.config.driver.(*txDriver) 74 | if !ok { 75 | panic("ent: User is not a transactional entity") 76 | } 77 | u.config.driver = _tx.drv 78 | return u 79 | } 80 | 81 | // String implements the fmt.Stringer. 82 | func (u *User) String() string { 83 | var builder strings.Builder 84 | builder.WriteString("User(") 85 | builder.WriteString(fmt.Sprintf("id=%v, ", u.ID)) 86 | builder.WriteString("name=") 87 | builder.WriteString(u.Name) 88 | builder.WriteByte(')') 89 | return builder.String() 90 | } 91 | 92 | // Users is a parsable slice of User. 93 | type Users []*User 94 | 95 | func (u Users) config(cfg config) { 96 | for _i := range u { 97 | u[_i].config = cfg 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /internal/middleware/log/interceptor.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "context" 5 | 6 | grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" 7 | grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus" 8 | "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus" 9 | "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 10 | "github.com/sirupsen/logrus" 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | ) 15 | 16 | var ( 17 | logMethodBlackList = []string{} 18 | ) 19 | 20 | func Interceptor(logger *logrus.Logger) func( 21 | ctx context.Context, 22 | req interface{}, 23 | _ *grpc.UnaryServerInfo, 24 | handler grpc.UnaryHandler) (interface{}, error) { 25 | ops := []grpc_logrus.Option{ 26 | grpc_logrus.WithLevels(levelFunc), 27 | grpc_logrus.WithDecider(decider), 28 | } 29 | entry := logrus.NewEntry(logger) 30 | 31 | logInterceptorBefore := createBeforeInterceptor(entry) 32 | logInterceptorAfter := createAfterInterceptor(entry) 33 | 34 | return grpc_middleware.ChainUnaryServer( 35 | logInterceptorBefore, 36 | grpc_logrus.UnaryServerInterceptor(entry, ops...), 37 | logInterceptorAfter, 38 | ) 39 | } 40 | 41 | func createBeforeInterceptor(entry *logrus.Entry) func( 42 | ctx context.Context, 43 | req interface{}, 44 | _ *grpc.UnaryServerInfo, 45 | handler grpc.UnaryHandler, 46 | ) (interface{}, error) { 47 | return func( 48 | ctx context.Context, 49 | req interface{}, 50 | _ *grpc.UnaryServerInfo, 51 | handler grpc.UnaryHandler, 52 | ) (interface{}, error) { 53 | newCtx := ctxlogrus.ToContext(ctx, entry) 54 | fields := logrus.Fields{} 55 | grpc_logrus.AddFields(newCtx, fields) 56 | return handler(newCtx, req) 57 | } 58 | } 59 | 60 | func createAfterInterceptor(entry *logrus.Entry) func( 61 | ctx context.Context, 62 | req interface{}, 63 | _ *grpc.UnaryServerInfo, 64 | handler grpc.UnaryHandler, 65 | ) (interface{}, error) { 66 | return func(ctx context.Context, 67 | req interface{}, 68 | _ *grpc.UnaryServerInfo, 69 | handler grpc.UnaryHandler, 70 | ) (interface{}, error) { 71 | fields := make(logrus.Fields) 72 | res, err := handler(ctx, req) 73 | s, ok := status.FromError(err) 74 | if ok { 75 | code := runtime.HTTPStatusFromCode(s.Code()) 76 | fields["code"] = code 77 | } 78 | if err != nil && ok { 79 | msg, _ := new(runtime.JSONPb).Marshal(s.Proto()) 80 | fields["resp"] = string(msg) 81 | } 82 | grpc_logrus.AddFields(ctx, fields) 83 | return res, err 84 | } 85 | } 86 | 87 | func levelFunc(c codes.Code) logrus.Level { 88 | switch c { 89 | case codes.Internal: 90 | return logrus.ErrorLevel 91 | case codes.InvalidArgument, 92 | codes.Unauthenticated, 93 | codes.PermissionDenied: 94 | return logrus.WarnLevel 95 | default: 96 | return logrus.InfoLevel 97 | } 98 | } 99 | 100 | func decider(fullMethodName string, err error) bool { 101 | if err != nil { 102 | return true 103 | } 104 | for _, name := range logMethodBlackList { 105 | if fullMethodName == name { 106 | return false 107 | } 108 | } 109 | return true 110 | } 111 | -------------------------------------------------------------------------------- /pkg/ent/pet_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "easycoding/pkg/ent/pet" 8 | "easycoding/pkg/ent/predicate" 9 | "fmt" 10 | 11 | "entgo.io/ent/dialect/sql" 12 | "entgo.io/ent/dialect/sql/sqlgraph" 13 | "entgo.io/ent/schema/field" 14 | ) 15 | 16 | // PetDelete is the builder for deleting a Pet entity. 17 | type PetDelete struct { 18 | config 19 | hooks []Hook 20 | mutation *PetMutation 21 | } 22 | 23 | // Where appends a list predicates to the PetDelete builder. 24 | func (pd *PetDelete) Where(ps ...predicate.Pet) *PetDelete { 25 | pd.mutation.Where(ps...) 26 | return pd 27 | } 28 | 29 | // Exec executes the deletion query and returns how many vertices were deleted. 30 | func (pd *PetDelete) Exec(ctx context.Context) (int, error) { 31 | var ( 32 | err error 33 | affected int 34 | ) 35 | if len(pd.hooks) == 0 { 36 | affected, err = pd.sqlExec(ctx) 37 | } else { 38 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 39 | mutation, ok := m.(*PetMutation) 40 | if !ok { 41 | return nil, fmt.Errorf("unexpected mutation type %T", m) 42 | } 43 | pd.mutation = mutation 44 | affected, err = pd.sqlExec(ctx) 45 | mutation.done = true 46 | return affected, err 47 | }) 48 | for i := len(pd.hooks) - 1; i >= 0; i-- { 49 | if pd.hooks[i] == nil { 50 | return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 51 | } 52 | mut = pd.hooks[i](mut) 53 | } 54 | if _, err := mut.Mutate(ctx, pd.mutation); err != nil { 55 | return 0, err 56 | } 57 | } 58 | return affected, err 59 | } 60 | 61 | // ExecX is like Exec, but panics if an error occurs. 62 | func (pd *PetDelete) ExecX(ctx context.Context) int { 63 | n, err := pd.Exec(ctx) 64 | if err != nil { 65 | panic(err) 66 | } 67 | return n 68 | } 69 | 70 | func (pd *PetDelete) sqlExec(ctx context.Context) (int, error) { 71 | _spec := &sqlgraph.DeleteSpec{ 72 | Node: &sqlgraph.NodeSpec{ 73 | Table: pet.Table, 74 | ID: &sqlgraph.FieldSpec{ 75 | Type: field.TypeInt, 76 | Column: pet.FieldID, 77 | }, 78 | }, 79 | } 80 | if ps := pd.mutation.predicates; len(ps) > 0 { 81 | _spec.Predicate = func(selector *sql.Selector) { 82 | for i := range ps { 83 | ps[i](selector) 84 | } 85 | } 86 | } 87 | affected, err := sqlgraph.DeleteNodes(ctx, pd.driver, _spec) 88 | if err != nil && sqlgraph.IsConstraintError(err) { 89 | err = &ConstraintError{msg: err.Error(), wrap: err} 90 | } 91 | return affected, err 92 | } 93 | 94 | // PetDeleteOne is the builder for deleting a single Pet entity. 95 | type PetDeleteOne struct { 96 | pd *PetDelete 97 | } 98 | 99 | // Exec executes the deletion query. 100 | func (pdo *PetDeleteOne) Exec(ctx context.Context) error { 101 | n, err := pdo.pd.Exec(ctx) 102 | switch { 103 | case err != nil: 104 | return err 105 | case n == 0: 106 | return &NotFoundError{pet.Label} 107 | default: 108 | return nil 109 | } 110 | } 111 | 112 | // ExecX is like Exec, but panics if an error occurs. 113 | func (pdo *PetDeleteOne) ExecX(ctx context.Context) { 114 | pdo.pd.ExecX(ctx) 115 | } 116 | -------------------------------------------------------------------------------- /pkg/ent/user_delete.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "easycoding/pkg/ent/predicate" 8 | "easycoding/pkg/ent/user" 9 | "fmt" 10 | 11 | "entgo.io/ent/dialect/sql" 12 | "entgo.io/ent/dialect/sql/sqlgraph" 13 | "entgo.io/ent/schema/field" 14 | ) 15 | 16 | // UserDelete is the builder for deleting a User entity. 17 | type UserDelete struct { 18 | config 19 | hooks []Hook 20 | mutation *UserMutation 21 | } 22 | 23 | // Where appends a list predicates to the UserDelete builder. 24 | func (ud *UserDelete) Where(ps ...predicate.User) *UserDelete { 25 | ud.mutation.Where(ps...) 26 | return ud 27 | } 28 | 29 | // Exec executes the deletion query and returns how many vertices were deleted. 30 | func (ud *UserDelete) Exec(ctx context.Context) (int, error) { 31 | var ( 32 | err error 33 | affected int 34 | ) 35 | if len(ud.hooks) == 0 { 36 | affected, err = ud.sqlExec(ctx) 37 | } else { 38 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 39 | mutation, ok := m.(*UserMutation) 40 | if !ok { 41 | return nil, fmt.Errorf("unexpected mutation type %T", m) 42 | } 43 | ud.mutation = mutation 44 | affected, err = ud.sqlExec(ctx) 45 | mutation.done = true 46 | return affected, err 47 | }) 48 | for i := len(ud.hooks) - 1; i >= 0; i-- { 49 | if ud.hooks[i] == nil { 50 | return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 51 | } 52 | mut = ud.hooks[i](mut) 53 | } 54 | if _, err := mut.Mutate(ctx, ud.mutation); err != nil { 55 | return 0, err 56 | } 57 | } 58 | return affected, err 59 | } 60 | 61 | // ExecX is like Exec, but panics if an error occurs. 62 | func (ud *UserDelete) ExecX(ctx context.Context) int { 63 | n, err := ud.Exec(ctx) 64 | if err != nil { 65 | panic(err) 66 | } 67 | return n 68 | } 69 | 70 | func (ud *UserDelete) sqlExec(ctx context.Context) (int, error) { 71 | _spec := &sqlgraph.DeleteSpec{ 72 | Node: &sqlgraph.NodeSpec{ 73 | Table: user.Table, 74 | ID: &sqlgraph.FieldSpec{ 75 | Type: field.TypeInt, 76 | Column: user.FieldID, 77 | }, 78 | }, 79 | } 80 | if ps := ud.mutation.predicates; len(ps) > 0 { 81 | _spec.Predicate = func(selector *sql.Selector) { 82 | for i := range ps { 83 | ps[i](selector) 84 | } 85 | } 86 | } 87 | affected, err := sqlgraph.DeleteNodes(ctx, ud.driver, _spec) 88 | if err != nil && sqlgraph.IsConstraintError(err) { 89 | err = &ConstraintError{msg: err.Error(), wrap: err} 90 | } 91 | return affected, err 92 | } 93 | 94 | // UserDeleteOne is the builder for deleting a single User entity. 95 | type UserDeleteOne struct { 96 | ud *UserDelete 97 | } 98 | 99 | // Exec executes the deletion query. 100 | func (udo *UserDeleteOne) Exec(ctx context.Context) error { 101 | n, err := udo.ud.Exec(ctx) 102 | switch { 103 | case err != nil: 104 | return err 105 | case n == 0: 106 | return &NotFoundError{user.Label} 107 | default: 108 | return nil 109 | } 110 | } 111 | 112 | // ExecX is like Exec, but panics if an error occurs. 113 | func (udo *UserDeleteOne) ExecX(ctx context.Context) { 114 | udo.ud.ExecX(ctx) 115 | } 116 | -------------------------------------------------------------------------------- /pkg/ent/migrate/migrate.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package migrate 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | "io" 9 | 10 | "entgo.io/ent/dialect" 11 | "entgo.io/ent/dialect/sql/schema" 12 | ) 13 | 14 | var ( 15 | // WithGlobalUniqueID sets the universal ids options to the migration. 16 | // If this option is enabled, ent migration will allocate a 1<<32 range 17 | // for the ids of each entity (table). 18 | // Note that this option cannot be applied on tables that already exist. 19 | WithGlobalUniqueID = schema.WithGlobalUniqueID 20 | // WithDropColumn sets the drop column option to the migration. 21 | // If this option is enabled, ent migration will drop old columns 22 | // that were used for both fields and edges. This defaults to false. 23 | WithDropColumn = schema.WithDropColumn 24 | // WithDropIndex sets the drop index option to the migration. 25 | // If this option is enabled, ent migration will drop old indexes 26 | // that were defined in the schema. This defaults to false. 27 | // Note that unique constraints are defined using `UNIQUE INDEX`, 28 | // and therefore, it's recommended to enable this option to get more 29 | // flexibility in the schema changes. 30 | WithDropIndex = schema.WithDropIndex 31 | // WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true. 32 | WithForeignKeys = schema.WithForeignKeys 33 | ) 34 | 35 | // Schema is the API for creating, migrating and dropping a schema. 36 | type Schema struct { 37 | drv dialect.Driver 38 | } 39 | 40 | // NewSchema creates a new schema client. 41 | func NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} } 42 | 43 | // Create creates all schema resources. 44 | func (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error { 45 | return Create(ctx, s, Tables, opts...) 46 | } 47 | 48 | // Create creates all table resources using the given schema driver. 49 | func Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...schema.MigrateOption) error { 50 | migrate, err := schema.NewMigrate(s.drv, opts...) 51 | if err != nil { 52 | return fmt.Errorf("ent/migrate: %w", err) 53 | } 54 | return migrate.Create(ctx, tables...) 55 | } 56 | 57 | // Diff compares the state read from a database connection or migration directory with 58 | // the state defined by the Ent schema. Changes will be written to new migration files. 59 | func Diff(ctx context.Context, url string, opts ...schema.MigrateOption) error { 60 | return NamedDiff(ctx, url, "changes", opts...) 61 | } 62 | 63 | // NamedDiff compares the state read from a database connection or migration directory with 64 | // the state defined by the Ent schema. Changes will be written to new named migration files. 65 | func NamedDiff(ctx context.Context, url, name string, opts ...schema.MigrateOption) error { 66 | return schema.Diff(ctx, url, name, Tables, opts...) 67 | } 68 | 69 | // Diff creates a migration file containing the statements to resolve the diff 70 | // between the Ent schema and the connected database. 71 | func (s *Schema) Diff(ctx context.Context, opts ...schema.MigrateOption) error { 72 | migrate, err := schema.NewMigrate(s.drv, opts...) 73 | if err != nil { 74 | return fmt.Errorf("ent/migrate: %w", err) 75 | } 76 | return migrate.Diff(ctx, Tables...) 77 | } 78 | 79 | // NamedDiff creates a named migration file containing the statements to resolve the diff 80 | // between the Ent schema and the connected database. 81 | func (s *Schema) NamedDiff(ctx context.Context, name string, opts ...schema.MigrateOption) error { 82 | migrate, err := schema.NewMigrate(s.drv, opts...) 83 | if err != nil { 84 | return fmt.Errorf("ent/migrate: %w", err) 85 | } 86 | return migrate.NamedDiff(ctx, name, Tables...) 87 | } 88 | 89 | // WriteTo writes the schema changes to w instead of running them against the database. 90 | // 91 | // if err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil { 92 | // log.Fatal(err) 93 | // } 94 | // 95 | func (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error { 96 | return Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv}}, Tables, opts...) 97 | } 98 | -------------------------------------------------------------------------------- /pkg/ent/pet.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "easycoding/pkg/ent/pet" 7 | "fmt" 8 | "strings" 9 | "time" 10 | 11 | "entgo.io/ent/dialect/sql" 12 | ) 13 | 14 | // Pet is the model entity for the Pet schema. 15 | type Pet struct { 16 | config `json:"-"` 17 | // ID of the ent. 18 | ID int `json:"id,omitempty"` 19 | // Name holds the value of the "name" field. 20 | Name string `json:"name,omitempty"` 21 | // Type holds the value of the "type" field. 22 | Type int8 `json:"type,omitempty"` 23 | // CreateAt holds the value of the "create_at" field. 24 | CreateAt time.Time `json:"create_at,omitempty"` 25 | } 26 | 27 | // scanValues returns the types for scanning values from sql.Rows. 28 | func (*Pet) scanValues(columns []string) ([]interface{}, error) { 29 | values := make([]interface{}, len(columns)) 30 | for i := range columns { 31 | switch columns[i] { 32 | case pet.FieldID, pet.FieldType: 33 | values[i] = new(sql.NullInt64) 34 | case pet.FieldName: 35 | values[i] = new(sql.NullString) 36 | case pet.FieldCreateAt: 37 | values[i] = new(sql.NullTime) 38 | default: 39 | return nil, fmt.Errorf("unexpected column %q for type Pet", columns[i]) 40 | } 41 | } 42 | return values, nil 43 | } 44 | 45 | // assignValues assigns the values that were returned from sql.Rows (after scanning) 46 | // to the Pet fields. 47 | func (pe *Pet) assignValues(columns []string, values []interface{}) error { 48 | if m, n := len(values), len(columns); m < n { 49 | return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) 50 | } 51 | for i := range columns { 52 | switch columns[i] { 53 | case pet.FieldID: 54 | value, ok := values[i].(*sql.NullInt64) 55 | if !ok { 56 | return fmt.Errorf("unexpected type %T for field id", value) 57 | } 58 | pe.ID = int(value.Int64) 59 | case pet.FieldName: 60 | if value, ok := values[i].(*sql.NullString); !ok { 61 | return fmt.Errorf("unexpected type %T for field name", values[i]) 62 | } else if value.Valid { 63 | pe.Name = value.String 64 | } 65 | case pet.FieldType: 66 | if value, ok := values[i].(*sql.NullInt64); !ok { 67 | return fmt.Errorf("unexpected type %T for field type", values[i]) 68 | } else if value.Valid { 69 | pe.Type = int8(value.Int64) 70 | } 71 | case pet.FieldCreateAt: 72 | if value, ok := values[i].(*sql.NullTime); !ok { 73 | return fmt.Errorf("unexpected type %T for field create_at", values[i]) 74 | } else if value.Valid { 75 | pe.CreateAt = value.Time 76 | } 77 | } 78 | } 79 | return nil 80 | } 81 | 82 | // Update returns a builder for updating this Pet. 83 | // Note that you need to call Pet.Unwrap() before calling this method if this Pet 84 | // was returned from a transaction, and the transaction was committed or rolled back. 85 | func (pe *Pet) Update() *PetUpdateOne { 86 | return (&PetClient{config: pe.config}).UpdateOne(pe) 87 | } 88 | 89 | // Unwrap unwraps the Pet entity that was returned from a transaction after it was closed, 90 | // so that all future queries will be executed through the driver which created the transaction. 91 | func (pe *Pet) Unwrap() *Pet { 92 | _tx, ok := pe.config.driver.(*txDriver) 93 | if !ok { 94 | panic("ent: Pet is not a transactional entity") 95 | } 96 | pe.config.driver = _tx.drv 97 | return pe 98 | } 99 | 100 | // String implements the fmt.Stringer. 101 | func (pe *Pet) String() string { 102 | var builder strings.Builder 103 | builder.WriteString("Pet(") 104 | builder.WriteString(fmt.Sprintf("id=%v, ", pe.ID)) 105 | builder.WriteString("name=") 106 | builder.WriteString(pe.Name) 107 | builder.WriteString(", ") 108 | builder.WriteString("type=") 109 | builder.WriteString(fmt.Sprintf("%v", pe.Type)) 110 | builder.WriteString(", ") 111 | builder.WriteString("create_at=") 112 | builder.WriteString(pe.CreateAt.Format(time.ANSIC)) 113 | builder.WriteByte(')') 114 | return builder.String() 115 | } 116 | 117 | // Pets is a parsable slice of Pet. 118 | type Pets []*Pet 119 | 120 | func (pe Pets) config(cfg config) { 121 | for _i := range pe { 122 | pe[_i].config = cfg 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /pkg/errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | pkgerrors "github.com/pkg/errors" 5 | ) 6 | 7 | const ( 8 | notFoundErrMsg = "not found error" 9 | internalErrMsg = "internal error" 10 | invalidErrMsg = "invalid request" 11 | permissionErrMsg = "permission error" 12 | unauthorizedErrMsg = "unauthorized error" 13 | ) 14 | 15 | var ( 16 | NotFoundError = notFoundError{} 17 | InternalError = internalError{} 18 | InvalidError = invalidError{} 19 | PermissionError = permissionError{} 20 | UnauthorizedError = unauthorizedError{} 21 | ) 22 | 23 | type notFoundError struct{} 24 | 25 | func (notFoundError) Error() string { return notFoundErrMsg } 26 | 27 | type internalError struct{} 28 | 29 | func (internalError) Error() string { return internalErrMsg } 30 | 31 | type invalidError struct{} 32 | 33 | func (invalidError) Error() string { return invalidErrMsg } 34 | 35 | type permissionError struct{} 36 | 37 | func (permissionError) Error() string { return permissionErrMsg } 38 | 39 | type unauthorizedError struct{} 40 | 41 | func (unauthorizedError) Error() string { return unauthorizedErrMsg } 42 | 43 | // ErrNotFound is an extend of codes.NotFound 44 | func ErrNotFound(err error) error { 45 | return wrapErr(NotFoundError, err) 46 | } 47 | 48 | // ErrNotFoundf is an extend of codes.Invalid 49 | func ErrNotFoundf(err error, format string, args ...interface{}) error { 50 | return wrapErrf(NotFoundError, err, format, args...) 51 | } 52 | 53 | func ErrNotFoundRaw(message string) error { 54 | return wrapErr(NotFoundError, pkgerrors.New(message)) 55 | } 56 | 57 | // ErrInternal is an extend of codes.Internal 58 | func ErrInternal(err error) error { 59 | return wrapErr(InternalError, err) 60 | } 61 | 62 | // ErrInternalf is an extend of codes.Internal 63 | func ErrInternalf(err error, format string, args ...interface{}) error { 64 | return wrapErrf(InternalError, err, format, args...) 65 | } 66 | 67 | func ErrInternalRaw(message string) error { 68 | return wrapErr(InternalError, pkgerrors.New(message)) 69 | } 70 | 71 | // ErrInvalid is an extend of codes.Invalid 72 | func ErrInvalid(err error) error { 73 | return wrapErr(InvalidError, err) 74 | } 75 | 76 | // ErrInvalidf is an extend of codes.Invalid 77 | func ErrInvalidf(err error, format string, args ...interface{}) error { 78 | return wrapErrf(InvalidError, err, format, args...) 79 | } 80 | 81 | func ErrInvalidRaw(message string) error { 82 | return wrapErr(InvalidError, pkgerrors.New(message)) 83 | } 84 | 85 | // ErrPermissionDenied is an extend of codes.PermissionDenied 86 | func ErrPermissionDenied(err error) error { 87 | return wrapErr(PermissionError, err) 88 | } 89 | 90 | // ErrPermissionDeniedf is an extend of codes.PermissionDenied 91 | func ErrPermissionDeniedf( 92 | err error, format string, args ...interface{}) error { 93 | return wrapErrf(PermissionError, err, format, args...) 94 | } 95 | 96 | func ErrPermissionDeniedRaw(message string) error { 97 | return wrapErr(PermissionError, pkgerrors.New(message)) 98 | } 99 | 100 | func ErrUnauthorized(err error) error { 101 | return wrapErr(UnauthorizedError, err) 102 | } 103 | 104 | func ErrUnauthorizedf(err error, format string, args ...interface{}) error { 105 | return wrapErrf(UnauthorizedError, err, format, args...) 106 | } 107 | 108 | func ErrUnauthorizedRaw(message string) error { 109 | return wrapErr(UnauthorizedError, pkgerrors.New(message)) 110 | } 111 | 112 | func ErrorIs(err error, target error) bool { 113 | return pkgerrors.Is(err, target) 114 | } 115 | 116 | func WithMessage(err error, message string) error { 117 | return pkgerrors.WithMessage(err, message) 118 | } 119 | 120 | func WithMessagef(err error, format string, args ...interface{}) error { 121 | return pkgerrors.WithMessagef(err, format, args...) 122 | } 123 | 124 | func wrapErr(wrapedErr error, err error) error { 125 | if err == nil { 126 | return pkgerrors.Wrap(wrapedErr, "") 127 | } 128 | return pkgerrors.Wrap(wrapedErr, err.Error()) 129 | } 130 | 131 | func wrapErrf( 132 | wrapedErr error, err error, format string, args ...interface{}) error { 133 | e := pkgerrors.WithMessagef(wrapedErr, format, args...) 134 | if err == nil { 135 | return pkgerrors.Wrap(wrapedErr, e.Error()) 136 | } 137 | return pkgerrors.Wrap(e, err.Error()) 138 | } 139 | -------------------------------------------------------------------------------- /api/third_party_apis/google/type/datetime.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.type; 18 | 19 | import "google/protobuf/duration.proto"; 20 | 21 | option cc_enable_arenas = true; 22 | option go_package = "google.golang.org/genproto/googleapis/type/datetime;datetime"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "DateTimeProto"; 25 | option java_package = "com.google.type"; 26 | option objc_class_prefix = "GTP"; 27 | 28 | // Represents civil time (or occasionally physical time). 29 | // 30 | // This type can represent a civil time in one of a few possible ways: 31 | // 32 | // * When utc_offset is set and time_zone is unset: a civil time on a calendar 33 | // day with a particular offset from UTC. 34 | // * When time_zone is set and utc_offset is unset: a civil time on a calendar 35 | // day in a particular time zone. 36 | // * When neither time_zone nor utc_offset is set: a civil time on a calendar 37 | // day in local time. 38 | // 39 | // The date is relative to the Proleptic Gregorian Calendar. 40 | // 41 | // If year is 0, the DateTime is considered not to have a specific year. month 42 | // and day must have valid, non-zero values. 43 | // 44 | // This type may also be used to represent a physical time if all the date and 45 | // time fields are set and either case of the `time_offset` oneof is set. 46 | // Consider using `Timestamp` message for physical time instead. If your use 47 | // case also would like to store the user's timezone, that can be done in 48 | // another field. 49 | // 50 | // This type is more flexible than some applications may want. Make sure to 51 | // document and validate your application's limitations. 52 | message DateTime { 53 | // Optional. Year of date. Must be from 1 to 9999, or 0 if specifying a 54 | // datetime without a year. 55 | int32 year = 1; 56 | 57 | // Required. Month of year. Must be from 1 to 12. 58 | int32 month = 2; 59 | 60 | // Required. Day of month. Must be from 1 to 31 and valid for the year and 61 | // month. 62 | int32 day = 3; 63 | 64 | // Required. Hours of day in 24 hour format. Should be from 0 to 23. An API 65 | // may choose to allow the value "24:00:00" for scenarios like business 66 | // closing time. 67 | int32 hours = 4; 68 | 69 | // Required. Minutes of hour of day. Must be from 0 to 59. 70 | int32 minutes = 5; 71 | 72 | // Required. Seconds of minutes of the time. Must normally be from 0 to 59. An 73 | // API may allow the value 60 if it allows leap-seconds. 74 | int32 seconds = 6; 75 | 76 | // Required. Fractions of seconds in nanoseconds. Must be from 0 to 77 | // 999,999,999. 78 | int32 nanos = 7; 79 | 80 | // Optional. Specifies either the UTC offset or the time zone of the DateTime. 81 | // Choose carefully between them, considering that time zone data may change 82 | // in the future (for example, a country modifies their DST start/end dates, 83 | // and future DateTimes in the affected range had already been stored). 84 | // If omitted, the DateTime is considered to be in local time. 85 | oneof time_offset { 86 | // UTC offset. Must be whole seconds, between -18 hours and +18 hours. 87 | // For example, a UTC offset of -4:00 would be represented as 88 | // { seconds: -14400 }. 89 | google.protobuf.Duration utc_offset = 8; 90 | 91 | // Time zone. 92 | TimeZone time_zone = 9; 93 | } 94 | } 95 | 96 | // Represents a time zone from the 97 | // [IANA Time Zone Database](https://www.iana.org/time-zones). 98 | message TimeZone { 99 | // IANA Time Zone Database time zone, e.g. "America/New_York". 100 | string id = 1; 101 | 102 | // Optional. IANA Time Zone Database version number, e.g. "2019a". 103 | string version = 2; 104 | } 105 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module easycoding 2 | 3 | go 1.21 4 | 5 | require ( 6 | ariga.io/atlas v0.15.0 7 | entgo.io/ent v0.12.5 8 | github.com/elazarl/go-bindata-assetfs v1.0.1 9 | github.com/envoyproxy/protoc-gen-validate v1.0.2 10 | github.com/fatih/color v1.16.0 11 | github.com/go-sql-driver/mysql v1.7.1 12 | github.com/golang-migrate/migrate/v4 v4.16.2 13 | github.com/gorilla/mux v1.8.1 14 | github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 15 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 16 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 17 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 18 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d 19 | github.com/pkg/errors v0.9.1 20 | github.com/prometheus/client_golang v1.17.0 21 | github.com/sirupsen/logrus v1.9.3 22 | github.com/spf13/cobra v1.8.0 23 | github.com/spf13/viper v1.17.0 24 | github.com/stretchr/testify v1.8.4 25 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 26 | go.opentelemetry.io/otel v1.21.0 27 | go.opentelemetry.io/otel/exporters/jaeger v1.17.0 28 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 29 | go.opentelemetry.io/otel/sdk v1.21.0 30 | go.opentelemetry.io/otel/trace v1.21.0 31 | golang.org/x/crypto v0.15.0 32 | google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 33 | google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 34 | google.golang.org/grpc v1.59.0 35 | google.golang.org/protobuf v1.31.0 36 | gopkg.in/yaml.v3 v3.0.1 37 | ) 38 | 39 | require ( 40 | github.com/agext/levenshtein v1.2.3 // indirect 41 | github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect 42 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 43 | github.com/beorn7/perks v1.0.1 // indirect 44 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 45 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 46 | github.com/fsnotify/fsnotify v1.7.0 // indirect 47 | github.com/go-logr/logr v1.3.0 // indirect 48 | github.com/go-logr/stdr v1.2.2 // indirect 49 | github.com/go-openapi/inflect v0.19.0 // indirect 50 | github.com/golang/protobuf v1.5.3 // indirect 51 | github.com/google/go-cmp v0.6.0 // indirect 52 | github.com/google/uuid v1.4.0 // indirect 53 | github.com/hashicorp/errwrap v1.1.0 // indirect 54 | github.com/hashicorp/go-multierror v1.1.1 // indirect 55 | github.com/hashicorp/hcl v1.0.0 // indirect 56 | github.com/hashicorp/hcl/v2 v2.19.1 // indirect 57 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 58 | github.com/magiconair/properties v1.8.7 // indirect 59 | github.com/mattn/go-colorable v0.1.13 // indirect 60 | github.com/mattn/go-isatty v0.0.20 // indirect 61 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 62 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 63 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 64 | github.com/mitchellh/mapstructure v1.5.0 // indirect 65 | github.com/pelletier/go-toml v1.9.5 // indirect 66 | github.com/pelletier/go-toml/v2 v2.1.0 // indirect 67 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 68 | github.com/prometheus/client_model v0.5.0 // indirect 69 | github.com/prometheus/common v0.45.0 // indirect 70 | github.com/prometheus/procfs v0.12.0 // indirect 71 | github.com/sagikazarmark/locafero v0.3.0 // indirect 72 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 73 | github.com/sergi/go-diff v1.1.0 // indirect 74 | github.com/sourcegraph/conc v0.3.0 // indirect 75 | github.com/spf13/afero v1.10.0 // indirect 76 | github.com/spf13/cast v1.5.1 // indirect 77 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 78 | github.com/spf13/pflag v1.0.5 // indirect 79 | github.com/subosito/gotenv v1.6.0 // indirect 80 | github.com/zclconf/go-cty v1.14.1 // indirect 81 | go.opentelemetry.io/otel/metric v1.21.0 // indirect 82 | go.uber.org/atomic v1.11.0 // indirect 83 | go.uber.org/multierr v1.11.0 // indirect 84 | golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect 85 | golang.org/x/mod v0.14.0 // indirect 86 | golang.org/x/net v0.18.0 // indirect 87 | golang.org/x/sys v0.14.0 // indirect 88 | golang.org/x/term v0.14.0 // indirect 89 | golang.org/x/text v0.14.0 // indirect 90 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect 91 | gopkg.in/ini.v1 v1.67.0 // indirect 92 | gopkg.in/yaml.v2 v2.4.0 // indirect 93 | ) 94 | -------------------------------------------------------------------------------- /pkg/ent/hook/hook.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package hook 4 | 5 | import ( 6 | "context" 7 | "easycoding/pkg/ent" 8 | "fmt" 9 | ) 10 | 11 | // The PetFunc type is an adapter to allow the use of ordinary 12 | // function as Pet mutator. 13 | type PetFunc func(context.Context, *ent.PetMutation) (ent.Value, error) 14 | 15 | // Mutate calls f(ctx, m). 16 | func (f PetFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { 17 | mv, ok := m.(*ent.PetMutation) 18 | if !ok { 19 | return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.PetMutation", m) 20 | } 21 | return f(ctx, mv) 22 | } 23 | 24 | // The UserFunc type is an adapter to allow the use of ordinary 25 | // function as User mutator. 26 | type UserFunc func(context.Context, *ent.UserMutation) (ent.Value, error) 27 | 28 | // Mutate calls f(ctx, m). 29 | func (f UserFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { 30 | mv, ok := m.(*ent.UserMutation) 31 | if !ok { 32 | return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UserMutation", m) 33 | } 34 | return f(ctx, mv) 35 | } 36 | 37 | // Condition is a hook condition function. 38 | type Condition func(context.Context, ent.Mutation) bool 39 | 40 | // And groups conditions with the AND operator. 41 | func And(first, second Condition, rest ...Condition) Condition { 42 | return func(ctx context.Context, m ent.Mutation) bool { 43 | if !first(ctx, m) || !second(ctx, m) { 44 | return false 45 | } 46 | for _, cond := range rest { 47 | if !cond(ctx, m) { 48 | return false 49 | } 50 | } 51 | return true 52 | } 53 | } 54 | 55 | // Or groups conditions with the OR operator. 56 | func Or(first, second Condition, rest ...Condition) Condition { 57 | return func(ctx context.Context, m ent.Mutation) bool { 58 | if first(ctx, m) || second(ctx, m) { 59 | return true 60 | } 61 | for _, cond := range rest { 62 | if cond(ctx, m) { 63 | return true 64 | } 65 | } 66 | return false 67 | } 68 | } 69 | 70 | // Not negates a given condition. 71 | func Not(cond Condition) Condition { 72 | return func(ctx context.Context, m ent.Mutation) bool { 73 | return !cond(ctx, m) 74 | } 75 | } 76 | 77 | // HasOp is a condition testing mutation operation. 78 | func HasOp(op ent.Op) Condition { 79 | return func(_ context.Context, m ent.Mutation) bool { 80 | return m.Op().Is(op) 81 | } 82 | } 83 | 84 | // HasAddedFields is a condition validating `.AddedField` on fields. 85 | func HasAddedFields(field string, fields ...string) Condition { 86 | return func(_ context.Context, m ent.Mutation) bool { 87 | if _, exists := m.AddedField(field); !exists { 88 | return false 89 | } 90 | for _, field := range fields { 91 | if _, exists := m.AddedField(field); !exists { 92 | return false 93 | } 94 | } 95 | return true 96 | } 97 | } 98 | 99 | // HasClearedFields is a condition validating `.FieldCleared` on fields. 100 | func HasClearedFields(field string, fields ...string) Condition { 101 | return func(_ context.Context, m ent.Mutation) bool { 102 | if exists := m.FieldCleared(field); !exists { 103 | return false 104 | } 105 | for _, field := range fields { 106 | if exists := m.FieldCleared(field); !exists { 107 | return false 108 | } 109 | } 110 | return true 111 | } 112 | } 113 | 114 | // HasFields is a condition validating `.Field` on fields. 115 | func HasFields(field string, fields ...string) Condition { 116 | return func(_ context.Context, m ent.Mutation) bool { 117 | if _, exists := m.Field(field); !exists { 118 | return false 119 | } 120 | for _, field := range fields { 121 | if _, exists := m.Field(field); !exists { 122 | return false 123 | } 124 | } 125 | return true 126 | } 127 | } 128 | 129 | // If executes the given hook under condition. 130 | // 131 | // hook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...))) 132 | // 133 | func If(hk ent.Hook, cond Condition) ent.Hook { 134 | return func(next ent.Mutator) ent.Mutator { 135 | return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) { 136 | if cond(ctx, m) { 137 | return hk(next).Mutate(ctx, m) 138 | } 139 | return next.Mutate(ctx, m) 140 | }) 141 | } 142 | } 143 | 144 | // On executes the given hook only for the given operation. 145 | // 146 | // hook.On(Log, ent.Delete|ent.Create) 147 | // 148 | func On(hk ent.Hook, op ent.Op) ent.Hook { 149 | return If(hk, HasOp(op)) 150 | } 151 | 152 | // Unless skips the given hook only for the given operation. 153 | // 154 | // hook.Unless(Log, ent.Update|ent.UpdateOne) 155 | // 156 | func Unless(hk ent.Hook, op ent.Op) ent.Hook { 157 | return If(hk, Not(HasOp(op))) 158 | } 159 | 160 | // FixedError is a hook returning a fixed error. 161 | func FixedError(err error) ent.Hook { 162 | return func(ent.Mutator) ent.Mutator { 163 | return ent.MutateFunc(func(context.Context, ent.Mutation) (ent.Value, error) { 164 | return nil, err 165 | }) 166 | } 167 | } 168 | 169 | // Reject returns a hook that rejects all operations that match op. 170 | // 171 | // func (T) Hooks() []ent.Hook { 172 | // return []ent.Hook{ 173 | // Reject(ent.Delete|ent.Update), 174 | // } 175 | // } 176 | // 177 | func Reject(op ent.Op) ent.Hook { 178 | hk := FixedError(fmt.Errorf("%s operation is not allowed", op)) 179 | return On(hk, op) 180 | } 181 | 182 | // Chain acts as a list of hooks and is effectively immutable. 183 | // Once created, it will always hold the same set of hooks in the same order. 184 | type Chain struct { 185 | hooks []ent.Hook 186 | } 187 | 188 | // NewChain creates a new chain of hooks. 189 | func NewChain(hooks ...ent.Hook) Chain { 190 | return Chain{append([]ent.Hook(nil), hooks...)} 191 | } 192 | 193 | // Hook chains the list of hooks and returns the final hook. 194 | func (c Chain) Hook() ent.Hook { 195 | return func(mutator ent.Mutator) ent.Mutator { 196 | for i := len(c.hooks) - 1; i >= 0; i-- { 197 | mutator = c.hooks[i](mutator) 198 | } 199 | return mutator 200 | } 201 | } 202 | 203 | // Append extends a chain, adding the specified hook 204 | // as the last ones in the mutation flow. 205 | func (c Chain) Append(hooks ...ent.Hook) Chain { 206 | newHooks := make([]ent.Hook, 0, len(c.hooks)+len(hooks)) 207 | newHooks = append(newHooks, c.hooks...) 208 | newHooks = append(newHooks, hooks...) 209 | return Chain{newHooks} 210 | } 211 | 212 | // Extend extends a chain, adding the specified chain 213 | // as the last ones in the mutation flow. 214 | func (c Chain) Extend(chain Chain) Chain { 215 | return c.Append(chain.hooks...) 216 | } 217 | -------------------------------------------------------------------------------- /pkg/ent/user/where.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package user 4 | 5 | import ( 6 | "easycoding/pkg/ent/predicate" 7 | 8 | "entgo.io/ent/dialect/sql" 9 | ) 10 | 11 | // ID filters vertices based on their ID field. 12 | func ID(id int) predicate.User { 13 | return predicate.User(func(s *sql.Selector) { 14 | s.Where(sql.EQ(s.C(FieldID), id)) 15 | }) 16 | } 17 | 18 | // IDEQ applies the EQ predicate on the ID field. 19 | func IDEQ(id int) predicate.User { 20 | return predicate.User(func(s *sql.Selector) { 21 | s.Where(sql.EQ(s.C(FieldID), id)) 22 | }) 23 | } 24 | 25 | // IDNEQ applies the NEQ predicate on the ID field. 26 | func IDNEQ(id int) predicate.User { 27 | return predicate.User(func(s *sql.Selector) { 28 | s.Where(sql.NEQ(s.C(FieldID), id)) 29 | }) 30 | } 31 | 32 | // IDIn applies the In predicate on the ID field. 33 | func IDIn(ids ...int) predicate.User { 34 | return predicate.User(func(s *sql.Selector) { 35 | v := make([]interface{}, len(ids)) 36 | for i := range v { 37 | v[i] = ids[i] 38 | } 39 | s.Where(sql.In(s.C(FieldID), v...)) 40 | }) 41 | } 42 | 43 | // IDNotIn applies the NotIn predicate on the ID field. 44 | func IDNotIn(ids ...int) predicate.User { 45 | return predicate.User(func(s *sql.Selector) { 46 | v := make([]interface{}, len(ids)) 47 | for i := range v { 48 | v[i] = ids[i] 49 | } 50 | s.Where(sql.NotIn(s.C(FieldID), v...)) 51 | }) 52 | } 53 | 54 | // IDGT applies the GT predicate on the ID field. 55 | func IDGT(id int) predicate.User { 56 | return predicate.User(func(s *sql.Selector) { 57 | s.Where(sql.GT(s.C(FieldID), id)) 58 | }) 59 | } 60 | 61 | // IDGTE applies the GTE predicate on the ID field. 62 | func IDGTE(id int) predicate.User { 63 | return predicate.User(func(s *sql.Selector) { 64 | s.Where(sql.GTE(s.C(FieldID), id)) 65 | }) 66 | } 67 | 68 | // IDLT applies the LT predicate on the ID field. 69 | func IDLT(id int) predicate.User { 70 | return predicate.User(func(s *sql.Selector) { 71 | s.Where(sql.LT(s.C(FieldID), id)) 72 | }) 73 | } 74 | 75 | // IDLTE applies the LTE predicate on the ID field. 76 | func IDLTE(id int) predicate.User { 77 | return predicate.User(func(s *sql.Selector) { 78 | s.Where(sql.LTE(s.C(FieldID), id)) 79 | }) 80 | } 81 | 82 | // Name applies equality check predicate on the "name" field. It's identical to NameEQ. 83 | func Name(v string) predicate.User { 84 | return predicate.User(func(s *sql.Selector) { 85 | s.Where(sql.EQ(s.C(FieldName), v)) 86 | }) 87 | } 88 | 89 | // NameEQ applies the EQ predicate on the "name" field. 90 | func NameEQ(v string) predicate.User { 91 | return predicate.User(func(s *sql.Selector) { 92 | s.Where(sql.EQ(s.C(FieldName), v)) 93 | }) 94 | } 95 | 96 | // NameNEQ applies the NEQ predicate on the "name" field. 97 | func NameNEQ(v string) predicate.User { 98 | return predicate.User(func(s *sql.Selector) { 99 | s.Where(sql.NEQ(s.C(FieldName), v)) 100 | }) 101 | } 102 | 103 | // NameIn applies the In predicate on the "name" field. 104 | func NameIn(vs ...string) predicate.User { 105 | v := make([]interface{}, len(vs)) 106 | for i := range v { 107 | v[i] = vs[i] 108 | } 109 | return predicate.User(func(s *sql.Selector) { 110 | s.Where(sql.In(s.C(FieldName), v...)) 111 | }) 112 | } 113 | 114 | // NameNotIn applies the NotIn predicate on the "name" field. 115 | func NameNotIn(vs ...string) predicate.User { 116 | v := make([]interface{}, len(vs)) 117 | for i := range v { 118 | v[i] = vs[i] 119 | } 120 | return predicate.User(func(s *sql.Selector) { 121 | s.Where(sql.NotIn(s.C(FieldName), v...)) 122 | }) 123 | } 124 | 125 | // NameGT applies the GT predicate on the "name" field. 126 | func NameGT(v string) predicate.User { 127 | return predicate.User(func(s *sql.Selector) { 128 | s.Where(sql.GT(s.C(FieldName), v)) 129 | }) 130 | } 131 | 132 | // NameGTE applies the GTE predicate on the "name" field. 133 | func NameGTE(v string) predicate.User { 134 | return predicate.User(func(s *sql.Selector) { 135 | s.Where(sql.GTE(s.C(FieldName), v)) 136 | }) 137 | } 138 | 139 | // NameLT applies the LT predicate on the "name" field. 140 | func NameLT(v string) predicate.User { 141 | return predicate.User(func(s *sql.Selector) { 142 | s.Where(sql.LT(s.C(FieldName), v)) 143 | }) 144 | } 145 | 146 | // NameLTE applies the LTE predicate on the "name" field. 147 | func NameLTE(v string) predicate.User { 148 | return predicate.User(func(s *sql.Selector) { 149 | s.Where(sql.LTE(s.C(FieldName), v)) 150 | }) 151 | } 152 | 153 | // NameContains applies the Contains predicate on the "name" field. 154 | func NameContains(v string) predicate.User { 155 | return predicate.User(func(s *sql.Selector) { 156 | s.Where(sql.Contains(s.C(FieldName), v)) 157 | }) 158 | } 159 | 160 | // NameHasPrefix applies the HasPrefix predicate on the "name" field. 161 | func NameHasPrefix(v string) predicate.User { 162 | return predicate.User(func(s *sql.Selector) { 163 | s.Where(sql.HasPrefix(s.C(FieldName), v)) 164 | }) 165 | } 166 | 167 | // NameHasSuffix applies the HasSuffix predicate on the "name" field. 168 | func NameHasSuffix(v string) predicate.User { 169 | return predicate.User(func(s *sql.Selector) { 170 | s.Where(sql.HasSuffix(s.C(FieldName), v)) 171 | }) 172 | } 173 | 174 | // NameEqualFold applies the EqualFold predicate on the "name" field. 175 | func NameEqualFold(v string) predicate.User { 176 | return predicate.User(func(s *sql.Selector) { 177 | s.Where(sql.EqualFold(s.C(FieldName), v)) 178 | }) 179 | } 180 | 181 | // NameContainsFold applies the ContainsFold predicate on the "name" field. 182 | func NameContainsFold(v string) predicate.User { 183 | return predicate.User(func(s *sql.Selector) { 184 | s.Where(sql.ContainsFold(s.C(FieldName), v)) 185 | }) 186 | } 187 | 188 | // And groups predicates with the AND operator between them. 189 | func And(predicates ...predicate.User) predicate.User { 190 | return predicate.User(func(s *sql.Selector) { 191 | s1 := s.Clone().SetP(nil) 192 | for _, p := range predicates { 193 | p(s1) 194 | } 195 | s.Where(s1.P()) 196 | }) 197 | } 198 | 199 | // Or groups predicates with the OR operator between them. 200 | func Or(predicates ...predicate.User) predicate.User { 201 | return predicate.User(func(s *sql.Selector) { 202 | s1 := s.Clone().SetP(nil) 203 | for i, p := range predicates { 204 | if i > 0 { 205 | s1.Or() 206 | } 207 | p(s1) 208 | } 209 | s.Where(s1.P()) 210 | }) 211 | } 212 | 213 | // Not applies the not operator on the given predicate. 214 | func Not(p predicate.User) predicate.User { 215 | return predicate.User(func(s *sql.Selector) { 216 | p(s.Not()) 217 | }) 218 | } 219 | -------------------------------------------------------------------------------- /pkg/ent/user_create.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "easycoding/pkg/ent/user" 8 | "errors" 9 | "fmt" 10 | 11 | "entgo.io/ent/dialect/sql/sqlgraph" 12 | "entgo.io/ent/schema/field" 13 | ) 14 | 15 | // UserCreate is the builder for creating a User entity. 16 | type UserCreate struct { 17 | config 18 | mutation *UserMutation 19 | hooks []Hook 20 | } 21 | 22 | // SetName sets the "name" field. 23 | func (uc *UserCreate) SetName(s string) *UserCreate { 24 | uc.mutation.SetName(s) 25 | return uc 26 | } 27 | 28 | // Mutation returns the UserMutation object of the builder. 29 | func (uc *UserCreate) Mutation() *UserMutation { 30 | return uc.mutation 31 | } 32 | 33 | // Save creates the User in the database. 34 | func (uc *UserCreate) Save(ctx context.Context) (*User, error) { 35 | var ( 36 | err error 37 | node *User 38 | ) 39 | if len(uc.hooks) == 0 { 40 | if err = uc.check(); err != nil { 41 | return nil, err 42 | } 43 | node, err = uc.sqlSave(ctx) 44 | } else { 45 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 46 | mutation, ok := m.(*UserMutation) 47 | if !ok { 48 | return nil, fmt.Errorf("unexpected mutation type %T", m) 49 | } 50 | if err = uc.check(); err != nil { 51 | return nil, err 52 | } 53 | uc.mutation = mutation 54 | if node, err = uc.sqlSave(ctx); err != nil { 55 | return nil, err 56 | } 57 | mutation.id = &node.ID 58 | mutation.done = true 59 | return node, err 60 | }) 61 | for i := len(uc.hooks) - 1; i >= 0; i-- { 62 | if uc.hooks[i] == nil { 63 | return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 64 | } 65 | mut = uc.hooks[i](mut) 66 | } 67 | v, err := mut.Mutate(ctx, uc.mutation) 68 | if err != nil { 69 | return nil, err 70 | } 71 | nv, ok := v.(*User) 72 | if !ok { 73 | return nil, fmt.Errorf("unexpected node type %T returned from UserMutation", v) 74 | } 75 | node = nv 76 | } 77 | return node, err 78 | } 79 | 80 | // SaveX calls Save and panics if Save returns an error. 81 | func (uc *UserCreate) SaveX(ctx context.Context) *User { 82 | v, err := uc.Save(ctx) 83 | if err != nil { 84 | panic(err) 85 | } 86 | return v 87 | } 88 | 89 | // Exec executes the query. 90 | func (uc *UserCreate) Exec(ctx context.Context) error { 91 | _, err := uc.Save(ctx) 92 | return err 93 | } 94 | 95 | // ExecX is like Exec, but panics if an error occurs. 96 | func (uc *UserCreate) ExecX(ctx context.Context) { 97 | if err := uc.Exec(ctx); err != nil { 98 | panic(err) 99 | } 100 | } 101 | 102 | // check runs all checks and user-defined validators on the builder. 103 | func (uc *UserCreate) check() error { 104 | if _, ok := uc.mutation.Name(); !ok { 105 | return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "User.name"`)} 106 | } 107 | return nil 108 | } 109 | 110 | func (uc *UserCreate) sqlSave(ctx context.Context) (*User, error) { 111 | _node, _spec := uc.createSpec() 112 | if err := sqlgraph.CreateNode(ctx, uc.driver, _spec); err != nil { 113 | if sqlgraph.IsConstraintError(err) { 114 | err = &ConstraintError{msg: err.Error(), wrap: err} 115 | } 116 | return nil, err 117 | } 118 | id := _spec.ID.Value.(int64) 119 | _node.ID = int(id) 120 | return _node, nil 121 | } 122 | 123 | func (uc *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { 124 | var ( 125 | _node = &User{config: uc.config} 126 | _spec = &sqlgraph.CreateSpec{ 127 | Table: user.Table, 128 | ID: &sqlgraph.FieldSpec{ 129 | Type: field.TypeInt, 130 | Column: user.FieldID, 131 | }, 132 | } 133 | ) 134 | if value, ok := uc.mutation.Name(); ok { 135 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 136 | Type: field.TypeString, 137 | Value: value, 138 | Column: user.FieldName, 139 | }) 140 | _node.Name = value 141 | } 142 | return _node, _spec 143 | } 144 | 145 | // UserCreateBulk is the builder for creating many User entities in bulk. 146 | type UserCreateBulk struct { 147 | config 148 | builders []*UserCreate 149 | } 150 | 151 | // Save creates the User entities in the database. 152 | func (ucb *UserCreateBulk) Save(ctx context.Context) ([]*User, error) { 153 | specs := make([]*sqlgraph.CreateSpec, len(ucb.builders)) 154 | nodes := make([]*User, len(ucb.builders)) 155 | mutators := make([]Mutator, len(ucb.builders)) 156 | for i := range ucb.builders { 157 | func(i int, root context.Context) { 158 | builder := ucb.builders[i] 159 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 160 | mutation, ok := m.(*UserMutation) 161 | if !ok { 162 | return nil, fmt.Errorf("unexpected mutation type %T", m) 163 | } 164 | if err := builder.check(); err != nil { 165 | return nil, err 166 | } 167 | builder.mutation = mutation 168 | nodes[i], specs[i] = builder.createSpec() 169 | var err error 170 | if i < len(mutators)-1 { 171 | _, err = mutators[i+1].Mutate(root, ucb.builders[i+1].mutation) 172 | } else { 173 | spec := &sqlgraph.BatchCreateSpec{Nodes: specs} 174 | // Invoke the actual operation on the latest mutation in the chain. 175 | if err = sqlgraph.BatchCreate(ctx, ucb.driver, spec); err != nil { 176 | if sqlgraph.IsConstraintError(err) { 177 | err = &ConstraintError{msg: err.Error(), wrap: err} 178 | } 179 | } 180 | } 181 | if err != nil { 182 | return nil, err 183 | } 184 | mutation.id = &nodes[i].ID 185 | if specs[i].ID.Value != nil { 186 | id := specs[i].ID.Value.(int64) 187 | nodes[i].ID = int(id) 188 | } 189 | mutation.done = true 190 | return nodes[i], nil 191 | }) 192 | for i := len(builder.hooks) - 1; i >= 0; i-- { 193 | mut = builder.hooks[i](mut) 194 | } 195 | mutators[i] = mut 196 | }(i, ctx) 197 | } 198 | if len(mutators) > 0 { 199 | if _, err := mutators[0].Mutate(ctx, ucb.builders[0].mutation); err != nil { 200 | return nil, err 201 | } 202 | } 203 | return nodes, nil 204 | } 205 | 206 | // SaveX is like Save, but panics if an error occurs. 207 | func (ucb *UserCreateBulk) SaveX(ctx context.Context) []*User { 208 | v, err := ucb.Save(ctx) 209 | if err != nil { 210 | panic(err) 211 | } 212 | return v 213 | } 214 | 215 | // Exec executes the query. 216 | func (ucb *UserCreateBulk) Exec(ctx context.Context) error { 217 | _, err := ucb.Save(ctx) 218 | return err 219 | } 220 | 221 | // ExecX is like Exec, but panics if an error occurs. 222 | func (ucb *UserCreateBulk) ExecX(ctx context.Context) { 223 | if err := ucb.Exec(ctx); err != nil { 224 | panic(err) 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /pkg/ent/tx.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "sync" 8 | 9 | "entgo.io/ent/dialect" 10 | ) 11 | 12 | // Tx is a transactional client that is created by calling Client.Tx(). 13 | type Tx struct { 14 | config 15 | // Pet is the client for interacting with the Pet builders. 16 | Pet *PetClient 17 | // User is the client for interacting with the User builders. 18 | User *UserClient 19 | 20 | // lazily loaded. 21 | client *Client 22 | clientOnce sync.Once 23 | 24 | // completion callbacks. 25 | mu sync.Mutex 26 | onCommit []CommitHook 27 | onRollback []RollbackHook 28 | 29 | // ctx lives for the life of the transaction. It is 30 | // the same context used by the underlying connection. 31 | ctx context.Context 32 | } 33 | 34 | type ( 35 | // Committer is the interface that wraps the Commit method. 36 | Committer interface { 37 | Commit(context.Context, *Tx) error 38 | } 39 | 40 | // The CommitFunc type is an adapter to allow the use of ordinary 41 | // function as a Committer. If f is a function with the appropriate 42 | // signature, CommitFunc(f) is a Committer that calls f. 43 | CommitFunc func(context.Context, *Tx) error 44 | 45 | // CommitHook defines the "commit middleware". A function that gets a Committer 46 | // and returns a Committer. For example: 47 | // 48 | // hook := func(next ent.Committer) ent.Committer { 49 | // return ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error { 50 | // // Do some stuff before. 51 | // if err := next.Commit(ctx, tx); err != nil { 52 | // return err 53 | // } 54 | // // Do some stuff after. 55 | // return nil 56 | // }) 57 | // } 58 | // 59 | CommitHook func(Committer) Committer 60 | ) 61 | 62 | // Commit calls f(ctx, m). 63 | func (f CommitFunc) Commit(ctx context.Context, tx *Tx) error { 64 | return f(ctx, tx) 65 | } 66 | 67 | // Commit commits the transaction. 68 | func (tx *Tx) Commit() error { 69 | txDriver := tx.config.driver.(*txDriver) 70 | var fn Committer = CommitFunc(func(context.Context, *Tx) error { 71 | return txDriver.tx.Commit() 72 | }) 73 | tx.mu.Lock() 74 | hooks := append([]CommitHook(nil), tx.onCommit...) 75 | tx.mu.Unlock() 76 | for i := len(hooks) - 1; i >= 0; i-- { 77 | fn = hooks[i](fn) 78 | } 79 | return fn.Commit(tx.ctx, tx) 80 | } 81 | 82 | // OnCommit adds a hook to call on commit. 83 | func (tx *Tx) OnCommit(f CommitHook) { 84 | tx.mu.Lock() 85 | defer tx.mu.Unlock() 86 | tx.onCommit = append(tx.onCommit, f) 87 | } 88 | 89 | type ( 90 | // Rollbacker is the interface that wraps the Rollback method. 91 | Rollbacker interface { 92 | Rollback(context.Context, *Tx) error 93 | } 94 | 95 | // The RollbackFunc type is an adapter to allow the use of ordinary 96 | // function as a Rollbacker. If f is a function with the appropriate 97 | // signature, RollbackFunc(f) is a Rollbacker that calls f. 98 | RollbackFunc func(context.Context, *Tx) error 99 | 100 | // RollbackHook defines the "rollback middleware". A function that gets a Rollbacker 101 | // and returns a Rollbacker. For example: 102 | // 103 | // hook := func(next ent.Rollbacker) ent.Rollbacker { 104 | // return ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error { 105 | // // Do some stuff before. 106 | // if err := next.Rollback(ctx, tx); err != nil { 107 | // return err 108 | // } 109 | // // Do some stuff after. 110 | // return nil 111 | // }) 112 | // } 113 | // 114 | RollbackHook func(Rollbacker) Rollbacker 115 | ) 116 | 117 | // Rollback calls f(ctx, m). 118 | func (f RollbackFunc) Rollback(ctx context.Context, tx *Tx) error { 119 | return f(ctx, tx) 120 | } 121 | 122 | // Rollback rollbacks the transaction. 123 | func (tx *Tx) Rollback() error { 124 | txDriver := tx.config.driver.(*txDriver) 125 | var fn Rollbacker = RollbackFunc(func(context.Context, *Tx) error { 126 | return txDriver.tx.Rollback() 127 | }) 128 | tx.mu.Lock() 129 | hooks := append([]RollbackHook(nil), tx.onRollback...) 130 | tx.mu.Unlock() 131 | for i := len(hooks) - 1; i >= 0; i-- { 132 | fn = hooks[i](fn) 133 | } 134 | return fn.Rollback(tx.ctx, tx) 135 | } 136 | 137 | // OnRollback adds a hook to call on rollback. 138 | func (tx *Tx) OnRollback(f RollbackHook) { 139 | tx.mu.Lock() 140 | defer tx.mu.Unlock() 141 | tx.onRollback = append(tx.onRollback, f) 142 | } 143 | 144 | // Client returns a Client that binds to current transaction. 145 | func (tx *Tx) Client() *Client { 146 | tx.clientOnce.Do(func() { 147 | tx.client = &Client{config: tx.config} 148 | tx.client.init() 149 | }) 150 | return tx.client 151 | } 152 | 153 | func (tx *Tx) init() { 154 | tx.Pet = NewPetClient(tx.config) 155 | tx.User = NewUserClient(tx.config) 156 | } 157 | 158 | // txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation. 159 | // The idea is to support transactions without adding any extra code to the builders. 160 | // When a builder calls to driver.Tx(), it gets the same dialect.Tx instance. 161 | // Commit and Rollback are nop for the internal builders and the user must call one 162 | // of them in order to commit or rollback the transaction. 163 | // 164 | // If a closed transaction is embedded in one of the generated entities, and the entity 165 | // applies a query, for example: Pet.QueryXXX(), the query will be executed 166 | // through the driver which created this transaction. 167 | // 168 | // Note that txDriver is not goroutine safe. 169 | type txDriver struct { 170 | // the driver we started the transaction from. 171 | drv dialect.Driver 172 | // tx is the underlying transaction. 173 | tx dialect.Tx 174 | } 175 | 176 | // newTx creates a new transactional driver. 177 | func newTx(ctx context.Context, drv dialect.Driver) (*txDriver, error) { 178 | tx, err := drv.Tx(ctx) 179 | if err != nil { 180 | return nil, err 181 | } 182 | return &txDriver{tx: tx, drv: drv}, nil 183 | } 184 | 185 | // Tx returns the transaction wrapper (txDriver) to avoid Commit or Rollback calls 186 | // from the internal builders. Should be called only by the internal builders. 187 | func (tx *txDriver) Tx(context.Context) (dialect.Tx, error) { return tx, nil } 188 | 189 | // Dialect returns the dialect of the driver we started the transaction from. 190 | func (tx *txDriver) Dialect() string { return tx.drv.Dialect() } 191 | 192 | // Close is a nop close. 193 | func (*txDriver) Close() error { return nil } 194 | 195 | // Commit is a nop commit for the internal builders. 196 | // User must call `Tx.Commit` in order to commit the transaction. 197 | func (*txDriver) Commit() error { return nil } 198 | 199 | // Rollback is a nop rollback for the internal builders. 200 | // User must call `Tx.Rollback` in order to rollback the transaction. 201 | func (*txDriver) Rollback() error { return nil } 202 | 203 | // Exec calls tx.Exec. 204 | func (tx *txDriver) Exec(ctx context.Context, query string, args, v interface{}) error { 205 | return tx.tx.Exec(ctx, query, args, v) 206 | } 207 | 208 | // Query calls tx.Query. 209 | func (tx *txDriver) Query(ctx context.Context, query string, args, v interface{}) error { 210 | return tx.tx.Query(ctx, query, args, v) 211 | } 212 | 213 | var _ dialect.Driver = (*txDriver)(nil) 214 | -------------------------------------------------------------------------------- /pkg/ent/user_update.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "easycoding/pkg/ent/predicate" 8 | "easycoding/pkg/ent/user" 9 | "errors" 10 | "fmt" 11 | 12 | "entgo.io/ent/dialect/sql" 13 | "entgo.io/ent/dialect/sql/sqlgraph" 14 | "entgo.io/ent/schema/field" 15 | ) 16 | 17 | // UserUpdate is the builder for updating User entities. 18 | type UserUpdate struct { 19 | config 20 | hooks []Hook 21 | mutation *UserMutation 22 | } 23 | 24 | // Where appends a list predicates to the UserUpdate builder. 25 | func (uu *UserUpdate) Where(ps ...predicate.User) *UserUpdate { 26 | uu.mutation.Where(ps...) 27 | return uu 28 | } 29 | 30 | // SetName sets the "name" field. 31 | func (uu *UserUpdate) SetName(s string) *UserUpdate { 32 | uu.mutation.SetName(s) 33 | return uu 34 | } 35 | 36 | // Mutation returns the UserMutation object of the builder. 37 | func (uu *UserUpdate) Mutation() *UserMutation { 38 | return uu.mutation 39 | } 40 | 41 | // Save executes the query and returns the number of nodes affected by the update operation. 42 | func (uu *UserUpdate) Save(ctx context.Context) (int, error) { 43 | var ( 44 | err error 45 | affected int 46 | ) 47 | if len(uu.hooks) == 0 { 48 | affected, err = uu.sqlSave(ctx) 49 | } else { 50 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 51 | mutation, ok := m.(*UserMutation) 52 | if !ok { 53 | return nil, fmt.Errorf("unexpected mutation type %T", m) 54 | } 55 | uu.mutation = mutation 56 | affected, err = uu.sqlSave(ctx) 57 | mutation.done = true 58 | return affected, err 59 | }) 60 | for i := len(uu.hooks) - 1; i >= 0; i-- { 61 | if uu.hooks[i] == nil { 62 | return 0, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 63 | } 64 | mut = uu.hooks[i](mut) 65 | } 66 | if _, err := mut.Mutate(ctx, uu.mutation); err != nil { 67 | return 0, err 68 | } 69 | } 70 | return affected, err 71 | } 72 | 73 | // SaveX is like Save, but panics if an error occurs. 74 | func (uu *UserUpdate) SaveX(ctx context.Context) int { 75 | affected, err := uu.Save(ctx) 76 | if err != nil { 77 | panic(err) 78 | } 79 | return affected 80 | } 81 | 82 | // Exec executes the query. 83 | func (uu *UserUpdate) Exec(ctx context.Context) error { 84 | _, err := uu.Save(ctx) 85 | return err 86 | } 87 | 88 | // ExecX is like Exec, but panics if an error occurs. 89 | func (uu *UserUpdate) ExecX(ctx context.Context) { 90 | if err := uu.Exec(ctx); err != nil { 91 | panic(err) 92 | } 93 | } 94 | 95 | func (uu *UserUpdate) sqlSave(ctx context.Context) (n int, err error) { 96 | _spec := &sqlgraph.UpdateSpec{ 97 | Node: &sqlgraph.NodeSpec{ 98 | Table: user.Table, 99 | Columns: user.Columns, 100 | ID: &sqlgraph.FieldSpec{ 101 | Type: field.TypeInt, 102 | Column: user.FieldID, 103 | }, 104 | }, 105 | } 106 | if ps := uu.mutation.predicates; len(ps) > 0 { 107 | _spec.Predicate = func(selector *sql.Selector) { 108 | for i := range ps { 109 | ps[i](selector) 110 | } 111 | } 112 | } 113 | if value, ok := uu.mutation.Name(); ok { 114 | _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ 115 | Type: field.TypeString, 116 | Value: value, 117 | Column: user.FieldName, 118 | }) 119 | } 120 | if n, err = sqlgraph.UpdateNodes(ctx, uu.driver, _spec); err != nil { 121 | if _, ok := err.(*sqlgraph.NotFoundError); ok { 122 | err = &NotFoundError{user.Label} 123 | } else if sqlgraph.IsConstraintError(err) { 124 | err = &ConstraintError{msg: err.Error(), wrap: err} 125 | } 126 | return 0, err 127 | } 128 | return n, nil 129 | } 130 | 131 | // UserUpdateOne is the builder for updating a single User entity. 132 | type UserUpdateOne struct { 133 | config 134 | fields []string 135 | hooks []Hook 136 | mutation *UserMutation 137 | } 138 | 139 | // SetName sets the "name" field. 140 | func (uuo *UserUpdateOne) SetName(s string) *UserUpdateOne { 141 | uuo.mutation.SetName(s) 142 | return uuo 143 | } 144 | 145 | // Mutation returns the UserMutation object of the builder. 146 | func (uuo *UserUpdateOne) Mutation() *UserMutation { 147 | return uuo.mutation 148 | } 149 | 150 | // Select allows selecting one or more fields (columns) of the returned entity. 151 | // The default is selecting all fields defined in the entity schema. 152 | func (uuo *UserUpdateOne) Select(field string, fields ...string) *UserUpdateOne { 153 | uuo.fields = append([]string{field}, fields...) 154 | return uuo 155 | } 156 | 157 | // Save executes the query and returns the updated User entity. 158 | func (uuo *UserUpdateOne) Save(ctx context.Context) (*User, error) { 159 | var ( 160 | err error 161 | node *User 162 | ) 163 | if len(uuo.hooks) == 0 { 164 | node, err = uuo.sqlSave(ctx) 165 | } else { 166 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 167 | mutation, ok := m.(*UserMutation) 168 | if !ok { 169 | return nil, fmt.Errorf("unexpected mutation type %T", m) 170 | } 171 | uuo.mutation = mutation 172 | node, err = uuo.sqlSave(ctx) 173 | mutation.done = true 174 | return node, err 175 | }) 176 | for i := len(uuo.hooks) - 1; i >= 0; i-- { 177 | if uuo.hooks[i] == nil { 178 | return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 179 | } 180 | mut = uuo.hooks[i](mut) 181 | } 182 | v, err := mut.Mutate(ctx, uuo.mutation) 183 | if err != nil { 184 | return nil, err 185 | } 186 | nv, ok := v.(*User) 187 | if !ok { 188 | return nil, fmt.Errorf("unexpected node type %T returned from UserMutation", v) 189 | } 190 | node = nv 191 | } 192 | return node, err 193 | } 194 | 195 | // SaveX is like Save, but panics if an error occurs. 196 | func (uuo *UserUpdateOne) SaveX(ctx context.Context) *User { 197 | node, err := uuo.Save(ctx) 198 | if err != nil { 199 | panic(err) 200 | } 201 | return node 202 | } 203 | 204 | // Exec executes the query on the entity. 205 | func (uuo *UserUpdateOne) Exec(ctx context.Context) error { 206 | _, err := uuo.Save(ctx) 207 | return err 208 | } 209 | 210 | // ExecX is like Exec, but panics if an error occurs. 211 | func (uuo *UserUpdateOne) ExecX(ctx context.Context) { 212 | if err := uuo.Exec(ctx); err != nil { 213 | panic(err) 214 | } 215 | } 216 | 217 | func (uuo *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) { 218 | _spec := &sqlgraph.UpdateSpec{ 219 | Node: &sqlgraph.NodeSpec{ 220 | Table: user.Table, 221 | Columns: user.Columns, 222 | ID: &sqlgraph.FieldSpec{ 223 | Type: field.TypeInt, 224 | Column: user.FieldID, 225 | }, 226 | }, 227 | } 228 | id, ok := uuo.mutation.ID() 229 | if !ok { 230 | return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "User.id" for update`)} 231 | } 232 | _spec.Node.ID.Value = id 233 | if fields := uuo.fields; len(fields) > 0 { 234 | _spec.Node.Columns = make([]string, 0, len(fields)) 235 | _spec.Node.Columns = append(_spec.Node.Columns, user.FieldID) 236 | for _, f := range fields { 237 | if !user.ValidColumn(f) { 238 | return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} 239 | } 240 | if f != user.FieldID { 241 | _spec.Node.Columns = append(_spec.Node.Columns, f) 242 | } 243 | } 244 | } 245 | if ps := uuo.mutation.predicates; len(ps) > 0 { 246 | _spec.Predicate = func(selector *sql.Selector) { 247 | for i := range ps { 248 | ps[i](selector) 249 | } 250 | } 251 | } 252 | if value, ok := uuo.mutation.Name(); ok { 253 | _spec.Fields.Set = append(_spec.Fields.Set, &sqlgraph.FieldSpec{ 254 | Type: field.TypeString, 255 | Value: value, 256 | Column: user.FieldName, 257 | }) 258 | } 259 | _node = &User{config: uuo.config} 260 | _spec.Assign = _node.assignValues 261 | _spec.ScanValues = _node.scanValues 262 | if err = sqlgraph.UpdateNode(ctx, uuo.driver, _spec); err != nil { 263 | if _, ok := err.(*sqlgraph.NotFoundError); ok { 264 | err = &NotFoundError{user.Label} 265 | } else if sqlgraph.IsConstraintError(err) { 266 | err = &ConstraintError{msg: err.Error(), wrap: err} 267 | } 268 | return nil, err 269 | } 270 | return _node, nil 271 | } 272 | -------------------------------------------------------------------------------- /pkg/ent/pet_create.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "easycoding/pkg/ent/pet" 8 | "errors" 9 | "fmt" 10 | "time" 11 | 12 | "entgo.io/ent/dialect/sql/sqlgraph" 13 | "entgo.io/ent/schema/field" 14 | ) 15 | 16 | // PetCreate is the builder for creating a Pet entity. 17 | type PetCreate struct { 18 | config 19 | mutation *PetMutation 20 | hooks []Hook 21 | } 22 | 23 | // SetName sets the "name" field. 24 | func (pc *PetCreate) SetName(s string) *PetCreate { 25 | pc.mutation.SetName(s) 26 | return pc 27 | } 28 | 29 | // SetType sets the "type" field. 30 | func (pc *PetCreate) SetType(i int8) *PetCreate { 31 | pc.mutation.SetType(i) 32 | return pc 33 | } 34 | 35 | // SetCreateAt sets the "create_at" field. 36 | func (pc *PetCreate) SetCreateAt(t time.Time) *PetCreate { 37 | pc.mutation.SetCreateAt(t) 38 | return pc 39 | } 40 | 41 | // SetNillableCreateAt sets the "create_at" field if the given value is not nil. 42 | func (pc *PetCreate) SetNillableCreateAt(t *time.Time) *PetCreate { 43 | if t != nil { 44 | pc.SetCreateAt(*t) 45 | } 46 | return pc 47 | } 48 | 49 | // Mutation returns the PetMutation object of the builder. 50 | func (pc *PetCreate) Mutation() *PetMutation { 51 | return pc.mutation 52 | } 53 | 54 | // Save creates the Pet in the database. 55 | func (pc *PetCreate) Save(ctx context.Context) (*Pet, error) { 56 | var ( 57 | err error 58 | node *Pet 59 | ) 60 | pc.defaults() 61 | if len(pc.hooks) == 0 { 62 | if err = pc.check(); err != nil { 63 | return nil, err 64 | } 65 | node, err = pc.sqlSave(ctx) 66 | } else { 67 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 68 | mutation, ok := m.(*PetMutation) 69 | if !ok { 70 | return nil, fmt.Errorf("unexpected mutation type %T", m) 71 | } 72 | if err = pc.check(); err != nil { 73 | return nil, err 74 | } 75 | pc.mutation = mutation 76 | if node, err = pc.sqlSave(ctx); err != nil { 77 | return nil, err 78 | } 79 | mutation.id = &node.ID 80 | mutation.done = true 81 | return node, err 82 | }) 83 | for i := len(pc.hooks) - 1; i >= 0; i-- { 84 | if pc.hooks[i] == nil { 85 | return nil, fmt.Errorf("ent: uninitialized hook (forgotten import ent/runtime?)") 86 | } 87 | mut = pc.hooks[i](mut) 88 | } 89 | v, err := mut.Mutate(ctx, pc.mutation) 90 | if err != nil { 91 | return nil, err 92 | } 93 | nv, ok := v.(*Pet) 94 | if !ok { 95 | return nil, fmt.Errorf("unexpected node type %T returned from PetMutation", v) 96 | } 97 | node = nv 98 | } 99 | return node, err 100 | } 101 | 102 | // SaveX calls Save and panics if Save returns an error. 103 | func (pc *PetCreate) SaveX(ctx context.Context) *Pet { 104 | v, err := pc.Save(ctx) 105 | if err != nil { 106 | panic(err) 107 | } 108 | return v 109 | } 110 | 111 | // Exec executes the query. 112 | func (pc *PetCreate) Exec(ctx context.Context) error { 113 | _, err := pc.Save(ctx) 114 | return err 115 | } 116 | 117 | // ExecX is like Exec, but panics if an error occurs. 118 | func (pc *PetCreate) ExecX(ctx context.Context) { 119 | if err := pc.Exec(ctx); err != nil { 120 | panic(err) 121 | } 122 | } 123 | 124 | // defaults sets the default values of the builder before save. 125 | func (pc *PetCreate) defaults() { 126 | if _, ok := pc.mutation.CreateAt(); !ok { 127 | v := pet.DefaultCreateAt 128 | pc.mutation.SetCreateAt(v) 129 | } 130 | } 131 | 132 | // check runs all checks and user-defined validators on the builder. 133 | func (pc *PetCreate) check() error { 134 | if _, ok := pc.mutation.Name(); !ok { 135 | return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "Pet.name"`)} 136 | } 137 | if v, ok := pc.mutation.Name(); ok { 138 | if err := pet.NameValidator(v); err != nil { 139 | return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "Pet.name": %w`, err)} 140 | } 141 | } 142 | if _, ok := pc.mutation.GetType(); !ok { 143 | return &ValidationError{Name: "type", err: errors.New(`ent: missing required field "Pet.type"`)} 144 | } 145 | if v, ok := pc.mutation.GetType(); ok { 146 | if err := pet.TypeValidator(v); err != nil { 147 | return &ValidationError{Name: "type", err: fmt.Errorf(`ent: validator failed for field "Pet.type": %w`, err)} 148 | } 149 | } 150 | if _, ok := pc.mutation.CreateAt(); !ok { 151 | return &ValidationError{Name: "create_at", err: errors.New(`ent: missing required field "Pet.create_at"`)} 152 | } 153 | return nil 154 | } 155 | 156 | func (pc *PetCreate) sqlSave(ctx context.Context) (*Pet, error) { 157 | _node, _spec := pc.createSpec() 158 | if err := sqlgraph.CreateNode(ctx, pc.driver, _spec); err != nil { 159 | if sqlgraph.IsConstraintError(err) { 160 | err = &ConstraintError{msg: err.Error(), wrap: err} 161 | } 162 | return nil, err 163 | } 164 | id := _spec.ID.Value.(int64) 165 | _node.ID = int(id) 166 | return _node, nil 167 | } 168 | 169 | func (pc *PetCreate) createSpec() (*Pet, *sqlgraph.CreateSpec) { 170 | var ( 171 | _node = &Pet{config: pc.config} 172 | _spec = &sqlgraph.CreateSpec{ 173 | Table: pet.Table, 174 | ID: &sqlgraph.FieldSpec{ 175 | Type: field.TypeInt, 176 | Column: pet.FieldID, 177 | }, 178 | } 179 | ) 180 | if value, ok := pc.mutation.Name(); ok { 181 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 182 | Type: field.TypeString, 183 | Value: value, 184 | Column: pet.FieldName, 185 | }) 186 | _node.Name = value 187 | } 188 | if value, ok := pc.mutation.GetType(); ok { 189 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 190 | Type: field.TypeInt8, 191 | Value: value, 192 | Column: pet.FieldType, 193 | }) 194 | _node.Type = value 195 | } 196 | if value, ok := pc.mutation.CreateAt(); ok { 197 | _spec.Fields = append(_spec.Fields, &sqlgraph.FieldSpec{ 198 | Type: field.TypeTime, 199 | Value: value, 200 | Column: pet.FieldCreateAt, 201 | }) 202 | _node.CreateAt = value 203 | } 204 | return _node, _spec 205 | } 206 | 207 | // PetCreateBulk is the builder for creating many Pet entities in bulk. 208 | type PetCreateBulk struct { 209 | config 210 | builders []*PetCreate 211 | } 212 | 213 | // Save creates the Pet entities in the database. 214 | func (pcb *PetCreateBulk) Save(ctx context.Context) ([]*Pet, error) { 215 | specs := make([]*sqlgraph.CreateSpec, len(pcb.builders)) 216 | nodes := make([]*Pet, len(pcb.builders)) 217 | mutators := make([]Mutator, len(pcb.builders)) 218 | for i := range pcb.builders { 219 | func(i int, root context.Context) { 220 | builder := pcb.builders[i] 221 | builder.defaults() 222 | var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { 223 | mutation, ok := m.(*PetMutation) 224 | if !ok { 225 | return nil, fmt.Errorf("unexpected mutation type %T", m) 226 | } 227 | if err := builder.check(); err != nil { 228 | return nil, err 229 | } 230 | builder.mutation = mutation 231 | nodes[i], specs[i] = builder.createSpec() 232 | var err error 233 | if i < len(mutators)-1 { 234 | _, err = mutators[i+1].Mutate(root, pcb.builders[i+1].mutation) 235 | } else { 236 | spec := &sqlgraph.BatchCreateSpec{Nodes: specs} 237 | // Invoke the actual operation on the latest mutation in the chain. 238 | if err = sqlgraph.BatchCreate(ctx, pcb.driver, spec); err != nil { 239 | if sqlgraph.IsConstraintError(err) { 240 | err = &ConstraintError{msg: err.Error(), wrap: err} 241 | } 242 | } 243 | } 244 | if err != nil { 245 | return nil, err 246 | } 247 | mutation.id = &nodes[i].ID 248 | if specs[i].ID.Value != nil { 249 | id := specs[i].ID.Value.(int64) 250 | nodes[i].ID = int(id) 251 | } 252 | mutation.done = true 253 | return nodes[i], nil 254 | }) 255 | for i := len(builder.hooks) - 1; i >= 0; i-- { 256 | mut = builder.hooks[i](mut) 257 | } 258 | mutators[i] = mut 259 | }(i, ctx) 260 | } 261 | if len(mutators) > 0 { 262 | if _, err := mutators[0].Mutate(ctx, pcb.builders[0].mutation); err != nil { 263 | return nil, err 264 | } 265 | } 266 | return nodes, nil 267 | } 268 | 269 | // SaveX is like Save, but panics if an error occurs. 270 | func (pcb *PetCreateBulk) SaveX(ctx context.Context) []*Pet { 271 | v, err := pcb.Save(ctx) 272 | if err != nil { 273 | panic(err) 274 | } 275 | return v 276 | } 277 | 278 | // Exec executes the query. 279 | func (pcb *PetCreateBulk) Exec(ctx context.Context) error { 280 | _, err := pcb.Save(ctx) 281 | return err 282 | } 283 | 284 | // ExecX is like Exec, but panics if an error occurs. 285 | func (pcb *PetCreateBulk) ExecX(ctx context.Context) { 286 | if err := pcb.Exec(ctx); err != nil { 287 | panic(err) 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /internal/app/kernel.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "context" 5 | "easycoding/internal/config" 6 | "fmt" 7 | 8 | auth_middleware "easycoding/internal/middleware/auth" 9 | error_middleware "easycoding/internal/middleware/error" 10 | log_middleware "easycoding/internal/middleware/log" 11 | otel_middleware "easycoding/internal/middleware/otel" 12 | prometheus_middleware "easycoding/internal/middleware/prometheus" 13 | recover_middleware "easycoding/internal/middleware/recover" 14 | validate_middleware "easycoding/internal/middleware/validate" 15 | "easycoding/internal/service" 16 | "easycoding/pkg/db" 17 | "easycoding/pkg/ent" 18 | "easycoding/pkg/log" 19 | 20 | pkg_otel "easycoding/pkg/otel" 21 | "easycoding/pkg/swagger" 22 | "io/ioutil" 23 | "net" 24 | "net/http" 25 | "os" 26 | "path/filepath" 27 | "sync" 28 | "time" 29 | 30 | assetfs "github.com/elazarl/go-bindata-assetfs" 31 | "github.com/gorilla/mux" 32 | grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" 33 | grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" 34 | "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 35 | "github.com/pkg/errors" 36 | "github.com/prometheus/client_golang/prometheus/promhttp" 37 | "github.com/sirupsen/logrus" 38 | "go.opentelemetry.io/otel/trace" 39 | "google.golang.org/grpc" 40 | "google.golang.org/grpc/keepalive" 41 | "google.golang.org/grpc/reflection" 42 | ) 43 | 44 | const ( 45 | version = "v0.0.1" 46 | ) 47 | 48 | // Possible application states. 49 | const ( 50 | StateStopped int = iota 51 | StateStarting 52 | StateRunning 53 | StateStopping 54 | ) 55 | 56 | const ( 57 | prometheusPrefix = "/metrics" 58 | swaggerPrefix = "/swagger/" 59 | 60 | // TODO(qujiabao): refactor these hard code values into config 61 | serveHost = "0.0.0.0" 62 | maxMsgSize = 5 * 1024 * 1024 63 | clientMinWaitPing = 5 * time.Second 64 | serverPingDuration = 10 * time.Minute 65 | ) 66 | 67 | type Kernel struct { 68 | log *logrus.Logger 69 | gwServer *http.Server 70 | grpcServer *grpc.Server 71 | swaggerServer *http.Server 72 | config *config.Config 73 | dbClient *ent.Client 74 | state int 75 | wg *sync.WaitGroup 76 | context cancelContext 77 | shutdownFns []func() error 78 | } 79 | 80 | func New(configPath string) (*Kernel, error) { 81 | config := config.LoadConfig(configPath) 82 | logger := log.New(os.Stderr, config.Log.Level, config.Log.Dir) 83 | tracer, shutdownTraceFunc, err := pkg_otel.NewTracer() 84 | if err != nil { 85 | return nil, errors.Wrap(err, "failed to new tracer") 86 | } 87 | database, err := db.CreateDBClient(config, tracer) 88 | if err != nil { 89 | return nil, errors.Wrap(err, "failed to connect to db") 90 | } 91 | // Create a global application context. 92 | ctx, cancel := context.WithCancel(context.Background()) 93 | 94 | gwServer := newGrpcGatewayServer(config, logger) 95 | grpcServer := newGrpcServer(config, logger, database, tracer) 96 | swaggerServer := newSwaggerServer(config) 97 | 98 | // Build the Kernel struct with all dependencies. 99 | app := &Kernel{ 100 | log: logger, 101 | config: config, 102 | dbClient: database, 103 | grpcServer: grpcServer, 104 | gwServer: gwServer, 105 | swaggerServer: swaggerServer, 106 | state: StateStarting, 107 | shutdownFns: []func() error{ 108 | shutdownTraceFunc, 109 | }, 110 | wg: &sync.WaitGroup{}, 111 | context: cancelContext{cancel: cancel, ctx: ctx}, 112 | } 113 | 114 | app.state = StateRunning 115 | 116 | return app, nil 117 | } 118 | 119 | func newGrpcServer( 120 | config *config.Config, 121 | logger *logrus.Logger, 122 | db *ent.Client, 123 | tracer trace.Tracer, 124 | ) *grpc.Server { 125 | opts := []grpc.ServerOption{ 126 | grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( 127 | log_middleware.Interceptor(logger), 128 | recover_middleware.Interceptor(), 129 | auth_middleware.Interceptor(), 130 | validate_middleware.Interceptor(), 131 | error_middleware.Interceptor(logger), 132 | prometheus_middleware.Interceptor(), 133 | otel_middleware.Interceptor(), 134 | )), 135 | grpc.MaxSendMsgSize(maxMsgSize), 136 | grpc.MaxRecvMsgSize(maxMsgSize), 137 | grpc.KeepaliveParams(keepalive.ServerParameters{ 138 | Time: serverPingDuration, 139 | }), 140 | grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{ 141 | MinTime: clientMinWaitPing, 142 | PermitWithoutStream: true, 143 | }), 144 | } 145 | // Create grpc server & register grpc services. 146 | grpcServer := grpc.NewServer(opts...) 147 | service.RegisterServers(grpcServer, logger, db, tracer) 148 | grpc_prometheus.EnableHandlingTimeHistogram() 149 | grpc_prometheus.Register(grpcServer) 150 | reflection.Register(grpcServer) 151 | return grpcServer 152 | } 153 | 154 | func newGrpcGatewayServer(config *config.Config, logger *logrus.Logger) *http.Server { 155 | gwmux := runtime.NewServeMux( 156 | runtime.WithMetadata(readFromRequest(logger)), 157 | ) 158 | router := mux.NewRouter() 159 | router.PathPrefix("/").Handler(gwmux) 160 | gwServer := &http.Server{ 161 | Addr: fmt.Sprintf("%s:%s", serveHost, config.Server.GatewayPort), 162 | Handler: router, 163 | } 164 | service.RegisterHandlers(gwmux, fmt.Sprintf("%s:%s", serveHost, config.Server.GrpcPort)) 165 | return gwServer 166 | } 167 | 168 | func newSwaggerServer(config *config.Config) *http.Server { 169 | router := mux.NewRouter() 170 | fileServer := http.FileServer(&assetfs.AssetFS{ 171 | Asset: swagger.Asset, 172 | AssetDir: swagger.AssetDir, 173 | AssetInfo: swagger.AssetInfo, 174 | // The swagger-ui is built from npm package swagger-ui-dist. 175 | Prefix: "dist", 176 | }) 177 | swaggerJSON, _ := filepath.Abs("api/api.swagger.json") 178 | jsonPrefix := fmt.Sprintf("%s%s", swaggerPrefix, "api.swagger.json") 179 | router.PathPrefix(jsonPrefix).Handler( 180 | http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 181 | f, err := ioutil.ReadFile(swaggerJSON) 182 | if err != nil { 183 | panic(err) 184 | } 185 | w.Write(f) 186 | })) 187 | 188 | router.PathPrefix(swaggerPrefix).Handler( 189 | http.StripPrefix(swaggerPrefix, fileServer)) 190 | router.PathPrefix(prometheusPrefix).Handler(promhttp.Handler()) 191 | 192 | server := &http.Server{ 193 | Addr: fmt.Sprintf("%s:%s", serveHost, config.Server.SwaggerPort), 194 | Handler: router, 195 | } 196 | return server 197 | } 198 | 199 | // cancelContext is a context with a cancel function. 200 | type cancelContext struct { 201 | cancel context.CancelFunc 202 | ctx context.Context 203 | } 204 | 205 | func (k *Kernel) listenAndServe(name string, listen func() error) { 206 | var err error 207 | for { 208 | // If the app is stopped or stopping, don't retry to start the server. 209 | if k.state == StateStopping || k.state == StateStopped { 210 | k.log.Tracef("skipping restarts of server because app is not in running state: state is %d", k.state) 211 | return 212 | } 213 | 214 | k.log.Infof("%s started\n", name) 215 | if err = listen(); err != nil { 216 | if k.config.Server.RestartOnError { 217 | k.log.Infof("restart server failed after error on %v", err) 218 | continue 219 | } 220 | k.log.Infof("server failed after error on %v", err) 221 | } 222 | return 223 | } 224 | 225 | } 226 | 227 | func (k *Kernel) ListenGrpcGateway() { 228 | listen := func() error { 229 | if err := k.gwServer.ListenAndServe(); err != nil { 230 | return err 231 | } 232 | return nil 233 | } 234 | descrition := fmt.Sprintf("grpc gateway server at %v", k.config.Server.GatewayPort) 235 | k.listenAndServe(descrition, listen) 236 | } 237 | 238 | func (k *Kernel) ListenGrpc() { 239 | listen := func() error { 240 | lis, err := net.Listen( 241 | "tcp", fmt.Sprintf("%s:%s", serveHost, k.config.Server.GrpcPort)) 242 | if err != nil { 243 | return err 244 | } 245 | if err := k.grpcServer.Serve(lis); err != nil { 246 | return err 247 | } 248 | return nil 249 | } 250 | descrition := fmt.Sprintf("grpc server at %v", k.config.Server.GrpcPort) 251 | k.listenAndServe(descrition, listen) 252 | } 253 | 254 | func (k *Kernel) ListenSwagger() { 255 | listen := func() error { 256 | if err := k.swaggerServer.ListenAndServe(); err != nil { 257 | return err 258 | } 259 | return nil 260 | } 261 | descrition := fmt.Sprintf( 262 | "swagger/metrics server at %v", k.config.Server.SwaggerPort) 263 | k.listenAndServe(descrition, listen) 264 | } 265 | 266 | func (k *Kernel) Shutdown(ctx context.Context) error { 267 | if k.state != StateRunning { 268 | k.log.Warn("Application cannot be shutdown since current state is not 'running'") 269 | return nil 270 | } 271 | 272 | k.state = StateStopping 273 | defer func() { 274 | k.state = StateStopped 275 | }() 276 | 277 | if k.gwServer != nil { 278 | if err := k.gwServer.Shutdown(ctx); err != nil { 279 | k.log.Errorf("server shutdown error: %v\n", err) 280 | } else { 281 | k.log.Infoln("gateway server stopped") 282 | } 283 | } 284 | 285 | if k.grpcServer != nil { 286 | k.grpcServer.GracefulStop() 287 | k.log.Infoln("grpc server stopped") 288 | } 289 | 290 | if k.swaggerServer != nil { 291 | if err := k.swaggerServer.Shutdown(ctx); err != nil { 292 | k.log.Errorf("swagger shutdown error: %v\n", err) 293 | } else { 294 | k.log.Infoln("swagger server stopped") 295 | } 296 | } 297 | 298 | // Cancel global context, then wait for all processes to quit. 299 | k.context.cancel() 300 | done := make(chan struct{}) 301 | go func() { 302 | k.wg.Wait() 303 | close(done) 304 | }() 305 | 306 | // Run shutdown functions. 307 | for _, fn := range k.shutdownFns { 308 | shutdownErr := fn() 309 | if shutdownErr != nil { 310 | k.log.Errorf("shutdown function returned error: %v\n", shutdownErr) 311 | } 312 | } 313 | 314 | select { 315 | case <-ctx.Done(): 316 | return ctx.Err() 317 | case <-done: 318 | } 319 | 320 | return k.dbClient.Close() 321 | } 322 | 323 | func Version() string { 324 | return version 325 | } 326 | -------------------------------------------------------------------------------- /pkg/ent/client.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package ent 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | "log" 10 | 11 | "easycoding/pkg/ent/migrate" 12 | 13 | "easycoding/pkg/ent/pet" 14 | "easycoding/pkg/ent/user" 15 | 16 | "entgo.io/ent/dialect" 17 | "entgo.io/ent/dialect/sql" 18 | ) 19 | 20 | // Client is the client that holds all ent builders. 21 | type Client struct { 22 | config 23 | // Schema is the client for creating, migrating and dropping schema. 24 | Schema *migrate.Schema 25 | // Pet is the client for interacting with the Pet builders. 26 | Pet *PetClient 27 | // User is the client for interacting with the User builders. 28 | User *UserClient 29 | } 30 | 31 | // NewClient creates a new client configured with the given options. 32 | func NewClient(opts ...Option) *Client { 33 | cfg := config{log: log.Println, hooks: &hooks{}} 34 | cfg.options(opts...) 35 | client := &Client{config: cfg} 36 | client.init() 37 | return client 38 | } 39 | 40 | func (c *Client) init() { 41 | c.Schema = migrate.NewSchema(c.driver) 42 | c.Pet = NewPetClient(c.config) 43 | c.User = NewUserClient(c.config) 44 | } 45 | 46 | // Open opens a database/sql.DB specified by the driver name and 47 | // the data source name, and returns a new client attached to it. 48 | // Optional parameters can be added for configuring the client. 49 | func Open(driverName, dataSourceName string, options ...Option) (*Client, error) { 50 | switch driverName { 51 | case dialect.MySQL, dialect.Postgres, dialect.SQLite: 52 | drv, err := sql.Open(driverName, dataSourceName) 53 | if err != nil { 54 | return nil, err 55 | } 56 | return NewClient(append(options, Driver(drv))...), nil 57 | default: 58 | return nil, fmt.Errorf("unsupported driver: %q", driverName) 59 | } 60 | } 61 | 62 | // Tx returns a new transactional client. The provided context 63 | // is used until the transaction is committed or rolled back. 64 | func (c *Client) Tx(ctx context.Context) (*Tx, error) { 65 | if _, ok := c.driver.(*txDriver); ok { 66 | return nil, errors.New("ent: cannot start a transaction within a transaction") 67 | } 68 | tx, err := newTx(ctx, c.driver) 69 | if err != nil { 70 | return nil, fmt.Errorf("ent: starting a transaction: %w", err) 71 | } 72 | cfg := c.config 73 | cfg.driver = tx 74 | return &Tx{ 75 | ctx: ctx, 76 | config: cfg, 77 | Pet: NewPetClient(cfg), 78 | User: NewUserClient(cfg), 79 | }, nil 80 | } 81 | 82 | // BeginTx returns a transactional client with specified options. 83 | func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { 84 | if _, ok := c.driver.(*txDriver); ok { 85 | return nil, errors.New("ent: cannot start a transaction within a transaction") 86 | } 87 | tx, err := c.driver.(interface { 88 | BeginTx(context.Context, *sql.TxOptions) (dialect.Tx, error) 89 | }).BeginTx(ctx, opts) 90 | if err != nil { 91 | return nil, fmt.Errorf("ent: starting a transaction: %w", err) 92 | } 93 | cfg := c.config 94 | cfg.driver = &txDriver{tx: tx, drv: c.driver} 95 | return &Tx{ 96 | ctx: ctx, 97 | config: cfg, 98 | Pet: NewPetClient(cfg), 99 | User: NewUserClient(cfg), 100 | }, nil 101 | } 102 | 103 | // Debug returns a new debug-client. It's used to get verbose logging on specific operations. 104 | // 105 | // client.Debug(). 106 | // Pet. 107 | // Query(). 108 | // Count(ctx) 109 | // 110 | func (c *Client) Debug() *Client { 111 | if c.debug { 112 | return c 113 | } 114 | cfg := c.config 115 | cfg.driver = dialect.Debug(c.driver, c.log) 116 | client := &Client{config: cfg} 117 | client.init() 118 | return client 119 | } 120 | 121 | // Close closes the database connection and prevents new queries from starting. 122 | func (c *Client) Close() error { 123 | return c.driver.Close() 124 | } 125 | 126 | // Use adds the mutation hooks to all the entity clients. 127 | // In order to add hooks to a specific client, call: `client.Node.Use(...)`. 128 | func (c *Client) Use(hooks ...Hook) { 129 | c.Pet.Use(hooks...) 130 | c.User.Use(hooks...) 131 | } 132 | 133 | // PetClient is a client for the Pet schema. 134 | type PetClient struct { 135 | config 136 | } 137 | 138 | // NewPetClient returns a client for the Pet from the given config. 139 | func NewPetClient(c config) *PetClient { 140 | return &PetClient{config: c} 141 | } 142 | 143 | // Use adds a list of mutation hooks to the hooks stack. 144 | // A call to `Use(f, g, h)` equals to `pet.Hooks(f(g(h())))`. 145 | func (c *PetClient) Use(hooks ...Hook) { 146 | c.hooks.Pet = append(c.hooks.Pet, hooks...) 147 | } 148 | 149 | // Create returns a builder for creating a Pet entity. 150 | func (c *PetClient) Create() *PetCreate { 151 | mutation := newPetMutation(c.config, OpCreate) 152 | return &PetCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} 153 | } 154 | 155 | // CreateBulk returns a builder for creating a bulk of Pet entities. 156 | func (c *PetClient) CreateBulk(builders ...*PetCreate) *PetCreateBulk { 157 | return &PetCreateBulk{config: c.config, builders: builders} 158 | } 159 | 160 | // Update returns an update builder for Pet. 161 | func (c *PetClient) Update() *PetUpdate { 162 | mutation := newPetMutation(c.config, OpUpdate) 163 | return &PetUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} 164 | } 165 | 166 | // UpdateOne returns an update builder for the given entity. 167 | func (c *PetClient) UpdateOne(pe *Pet) *PetUpdateOne { 168 | mutation := newPetMutation(c.config, OpUpdateOne, withPet(pe)) 169 | return &PetUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} 170 | } 171 | 172 | // UpdateOneID returns an update builder for the given id. 173 | func (c *PetClient) UpdateOneID(id int) *PetUpdateOne { 174 | mutation := newPetMutation(c.config, OpUpdateOne, withPetID(id)) 175 | return &PetUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} 176 | } 177 | 178 | // Delete returns a delete builder for Pet. 179 | func (c *PetClient) Delete() *PetDelete { 180 | mutation := newPetMutation(c.config, OpDelete) 181 | return &PetDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} 182 | } 183 | 184 | // DeleteOne returns a builder for deleting the given entity. 185 | func (c *PetClient) DeleteOne(pe *Pet) *PetDeleteOne { 186 | return c.DeleteOneID(pe.ID) 187 | } 188 | 189 | // DeleteOne returns a builder for deleting the given entity by its id. 190 | func (c *PetClient) DeleteOneID(id int) *PetDeleteOne { 191 | builder := c.Delete().Where(pet.ID(id)) 192 | builder.mutation.id = &id 193 | builder.mutation.op = OpDeleteOne 194 | return &PetDeleteOne{builder} 195 | } 196 | 197 | // Query returns a query builder for Pet. 198 | func (c *PetClient) Query() *PetQuery { 199 | return &PetQuery{ 200 | config: c.config, 201 | } 202 | } 203 | 204 | // Get returns a Pet entity by its id. 205 | func (c *PetClient) Get(ctx context.Context, id int) (*Pet, error) { 206 | return c.Query().Where(pet.ID(id)).Only(ctx) 207 | } 208 | 209 | // GetX is like Get, but panics if an error occurs. 210 | func (c *PetClient) GetX(ctx context.Context, id int) *Pet { 211 | obj, err := c.Get(ctx, id) 212 | if err != nil { 213 | panic(err) 214 | } 215 | return obj 216 | } 217 | 218 | // Hooks returns the client hooks. 219 | func (c *PetClient) Hooks() []Hook { 220 | return c.hooks.Pet 221 | } 222 | 223 | // UserClient is a client for the User schema. 224 | type UserClient struct { 225 | config 226 | } 227 | 228 | // NewUserClient returns a client for the User from the given config. 229 | func NewUserClient(c config) *UserClient { 230 | return &UserClient{config: c} 231 | } 232 | 233 | // Use adds a list of mutation hooks to the hooks stack. 234 | // A call to `Use(f, g, h)` equals to `user.Hooks(f(g(h())))`. 235 | func (c *UserClient) Use(hooks ...Hook) { 236 | c.hooks.User = append(c.hooks.User, hooks...) 237 | } 238 | 239 | // Create returns a builder for creating a User entity. 240 | func (c *UserClient) Create() *UserCreate { 241 | mutation := newUserMutation(c.config, OpCreate) 242 | return &UserCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} 243 | } 244 | 245 | // CreateBulk returns a builder for creating a bulk of User entities. 246 | func (c *UserClient) CreateBulk(builders ...*UserCreate) *UserCreateBulk { 247 | return &UserCreateBulk{config: c.config, builders: builders} 248 | } 249 | 250 | // Update returns an update builder for User. 251 | func (c *UserClient) Update() *UserUpdate { 252 | mutation := newUserMutation(c.config, OpUpdate) 253 | return &UserUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} 254 | } 255 | 256 | // UpdateOne returns an update builder for the given entity. 257 | func (c *UserClient) UpdateOne(u *User) *UserUpdateOne { 258 | mutation := newUserMutation(c.config, OpUpdateOne, withUser(u)) 259 | return &UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} 260 | } 261 | 262 | // UpdateOneID returns an update builder for the given id. 263 | func (c *UserClient) UpdateOneID(id int) *UserUpdateOne { 264 | mutation := newUserMutation(c.config, OpUpdateOne, withUserID(id)) 265 | return &UserUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} 266 | } 267 | 268 | // Delete returns a delete builder for User. 269 | func (c *UserClient) Delete() *UserDelete { 270 | mutation := newUserMutation(c.config, OpDelete) 271 | return &UserDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} 272 | } 273 | 274 | // DeleteOne returns a builder for deleting the given entity. 275 | func (c *UserClient) DeleteOne(u *User) *UserDeleteOne { 276 | return c.DeleteOneID(u.ID) 277 | } 278 | 279 | // DeleteOne returns a builder for deleting the given entity by its id. 280 | func (c *UserClient) DeleteOneID(id int) *UserDeleteOne { 281 | builder := c.Delete().Where(user.ID(id)) 282 | builder.mutation.id = &id 283 | builder.mutation.op = OpDeleteOne 284 | return &UserDeleteOne{builder} 285 | } 286 | 287 | // Query returns a query builder for User. 288 | func (c *UserClient) Query() *UserQuery { 289 | return &UserQuery{ 290 | config: c.config, 291 | } 292 | } 293 | 294 | // Get returns a User entity by its id. 295 | func (c *UserClient) Get(ctx context.Context, id int) (*User, error) { 296 | return c.Query().Where(user.ID(id)).Only(ctx) 297 | } 298 | 299 | // GetX is like Get, but panics if an error occurs. 300 | func (c *UserClient) GetX(ctx context.Context, id int) *User { 301 | obj, err := c.Get(ctx, id) 302 | if err != nil { 303 | panic(err) 304 | } 305 | return obj 306 | } 307 | 308 | // Hooks returns the client hooks. 309 | func (c *UserClient) Hooks() []Hook { 310 | return c.hooks.User 311 | } 312 | -------------------------------------------------------------------------------- /pkg/ent/pet/where.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package pet 4 | 5 | import ( 6 | "easycoding/pkg/ent/predicate" 7 | "time" 8 | 9 | "entgo.io/ent/dialect/sql" 10 | ) 11 | 12 | // ID filters vertices based on their ID field. 13 | func ID(id int) predicate.Pet { 14 | return predicate.Pet(func(s *sql.Selector) { 15 | s.Where(sql.EQ(s.C(FieldID), id)) 16 | }) 17 | } 18 | 19 | // IDEQ applies the EQ predicate on the ID field. 20 | func IDEQ(id int) predicate.Pet { 21 | return predicate.Pet(func(s *sql.Selector) { 22 | s.Where(sql.EQ(s.C(FieldID), id)) 23 | }) 24 | } 25 | 26 | // IDNEQ applies the NEQ predicate on the ID field. 27 | func IDNEQ(id int) predicate.Pet { 28 | return predicate.Pet(func(s *sql.Selector) { 29 | s.Where(sql.NEQ(s.C(FieldID), id)) 30 | }) 31 | } 32 | 33 | // IDIn applies the In predicate on the ID field. 34 | func IDIn(ids ...int) predicate.Pet { 35 | return predicate.Pet(func(s *sql.Selector) { 36 | v := make([]interface{}, len(ids)) 37 | for i := range v { 38 | v[i] = ids[i] 39 | } 40 | s.Where(sql.In(s.C(FieldID), v...)) 41 | }) 42 | } 43 | 44 | // IDNotIn applies the NotIn predicate on the ID field. 45 | func IDNotIn(ids ...int) predicate.Pet { 46 | return predicate.Pet(func(s *sql.Selector) { 47 | v := make([]interface{}, len(ids)) 48 | for i := range v { 49 | v[i] = ids[i] 50 | } 51 | s.Where(sql.NotIn(s.C(FieldID), v...)) 52 | }) 53 | } 54 | 55 | // IDGT applies the GT predicate on the ID field. 56 | func IDGT(id int) predicate.Pet { 57 | return predicate.Pet(func(s *sql.Selector) { 58 | s.Where(sql.GT(s.C(FieldID), id)) 59 | }) 60 | } 61 | 62 | // IDGTE applies the GTE predicate on the ID field. 63 | func IDGTE(id int) predicate.Pet { 64 | return predicate.Pet(func(s *sql.Selector) { 65 | s.Where(sql.GTE(s.C(FieldID), id)) 66 | }) 67 | } 68 | 69 | // IDLT applies the LT predicate on the ID field. 70 | func IDLT(id int) predicate.Pet { 71 | return predicate.Pet(func(s *sql.Selector) { 72 | s.Where(sql.LT(s.C(FieldID), id)) 73 | }) 74 | } 75 | 76 | // IDLTE applies the LTE predicate on the ID field. 77 | func IDLTE(id int) predicate.Pet { 78 | return predicate.Pet(func(s *sql.Selector) { 79 | s.Where(sql.LTE(s.C(FieldID), id)) 80 | }) 81 | } 82 | 83 | // Name applies equality check predicate on the "name" field. It's identical to NameEQ. 84 | func Name(v string) predicate.Pet { 85 | return predicate.Pet(func(s *sql.Selector) { 86 | s.Where(sql.EQ(s.C(FieldName), v)) 87 | }) 88 | } 89 | 90 | // Type applies equality check predicate on the "type" field. It's identical to TypeEQ. 91 | func Type(v int8) predicate.Pet { 92 | return predicate.Pet(func(s *sql.Selector) { 93 | s.Where(sql.EQ(s.C(FieldType), v)) 94 | }) 95 | } 96 | 97 | // CreateAt applies equality check predicate on the "create_at" field. It's identical to CreateAtEQ. 98 | func CreateAt(v time.Time) predicate.Pet { 99 | return predicate.Pet(func(s *sql.Selector) { 100 | s.Where(sql.EQ(s.C(FieldCreateAt), v)) 101 | }) 102 | } 103 | 104 | // NameEQ applies the EQ predicate on the "name" field. 105 | func NameEQ(v string) predicate.Pet { 106 | return predicate.Pet(func(s *sql.Selector) { 107 | s.Where(sql.EQ(s.C(FieldName), v)) 108 | }) 109 | } 110 | 111 | // NameNEQ applies the NEQ predicate on the "name" field. 112 | func NameNEQ(v string) predicate.Pet { 113 | return predicate.Pet(func(s *sql.Selector) { 114 | s.Where(sql.NEQ(s.C(FieldName), v)) 115 | }) 116 | } 117 | 118 | // NameIn applies the In predicate on the "name" field. 119 | func NameIn(vs ...string) predicate.Pet { 120 | v := make([]interface{}, len(vs)) 121 | for i := range v { 122 | v[i] = vs[i] 123 | } 124 | return predicate.Pet(func(s *sql.Selector) { 125 | s.Where(sql.In(s.C(FieldName), v...)) 126 | }) 127 | } 128 | 129 | // NameNotIn applies the NotIn predicate on the "name" field. 130 | func NameNotIn(vs ...string) predicate.Pet { 131 | v := make([]interface{}, len(vs)) 132 | for i := range v { 133 | v[i] = vs[i] 134 | } 135 | return predicate.Pet(func(s *sql.Selector) { 136 | s.Where(sql.NotIn(s.C(FieldName), v...)) 137 | }) 138 | } 139 | 140 | // NameGT applies the GT predicate on the "name" field. 141 | func NameGT(v string) predicate.Pet { 142 | return predicate.Pet(func(s *sql.Selector) { 143 | s.Where(sql.GT(s.C(FieldName), v)) 144 | }) 145 | } 146 | 147 | // NameGTE applies the GTE predicate on the "name" field. 148 | func NameGTE(v string) predicate.Pet { 149 | return predicate.Pet(func(s *sql.Selector) { 150 | s.Where(sql.GTE(s.C(FieldName), v)) 151 | }) 152 | } 153 | 154 | // NameLT applies the LT predicate on the "name" field. 155 | func NameLT(v string) predicate.Pet { 156 | return predicate.Pet(func(s *sql.Selector) { 157 | s.Where(sql.LT(s.C(FieldName), v)) 158 | }) 159 | } 160 | 161 | // NameLTE applies the LTE predicate on the "name" field. 162 | func NameLTE(v string) predicate.Pet { 163 | return predicate.Pet(func(s *sql.Selector) { 164 | s.Where(sql.LTE(s.C(FieldName), v)) 165 | }) 166 | } 167 | 168 | // NameContains applies the Contains predicate on the "name" field. 169 | func NameContains(v string) predicate.Pet { 170 | return predicate.Pet(func(s *sql.Selector) { 171 | s.Where(sql.Contains(s.C(FieldName), v)) 172 | }) 173 | } 174 | 175 | // NameHasPrefix applies the HasPrefix predicate on the "name" field. 176 | func NameHasPrefix(v string) predicate.Pet { 177 | return predicate.Pet(func(s *sql.Selector) { 178 | s.Where(sql.HasPrefix(s.C(FieldName), v)) 179 | }) 180 | } 181 | 182 | // NameHasSuffix applies the HasSuffix predicate on the "name" field. 183 | func NameHasSuffix(v string) predicate.Pet { 184 | return predicate.Pet(func(s *sql.Selector) { 185 | s.Where(sql.HasSuffix(s.C(FieldName), v)) 186 | }) 187 | } 188 | 189 | // NameEqualFold applies the EqualFold predicate on the "name" field. 190 | func NameEqualFold(v string) predicate.Pet { 191 | return predicate.Pet(func(s *sql.Selector) { 192 | s.Where(sql.EqualFold(s.C(FieldName), v)) 193 | }) 194 | } 195 | 196 | // NameContainsFold applies the ContainsFold predicate on the "name" field. 197 | func NameContainsFold(v string) predicate.Pet { 198 | return predicate.Pet(func(s *sql.Selector) { 199 | s.Where(sql.ContainsFold(s.C(FieldName), v)) 200 | }) 201 | } 202 | 203 | // TypeEQ applies the EQ predicate on the "type" field. 204 | func TypeEQ(v int8) predicate.Pet { 205 | return predicate.Pet(func(s *sql.Selector) { 206 | s.Where(sql.EQ(s.C(FieldType), v)) 207 | }) 208 | } 209 | 210 | // TypeNEQ applies the NEQ predicate on the "type" field. 211 | func TypeNEQ(v int8) predicate.Pet { 212 | return predicate.Pet(func(s *sql.Selector) { 213 | s.Where(sql.NEQ(s.C(FieldType), v)) 214 | }) 215 | } 216 | 217 | // TypeIn applies the In predicate on the "type" field. 218 | func TypeIn(vs ...int8) predicate.Pet { 219 | v := make([]interface{}, len(vs)) 220 | for i := range v { 221 | v[i] = vs[i] 222 | } 223 | return predicate.Pet(func(s *sql.Selector) { 224 | s.Where(sql.In(s.C(FieldType), v...)) 225 | }) 226 | } 227 | 228 | // TypeNotIn applies the NotIn predicate on the "type" field. 229 | func TypeNotIn(vs ...int8) predicate.Pet { 230 | v := make([]interface{}, len(vs)) 231 | for i := range v { 232 | v[i] = vs[i] 233 | } 234 | return predicate.Pet(func(s *sql.Selector) { 235 | s.Where(sql.NotIn(s.C(FieldType), v...)) 236 | }) 237 | } 238 | 239 | // TypeGT applies the GT predicate on the "type" field. 240 | func TypeGT(v int8) predicate.Pet { 241 | return predicate.Pet(func(s *sql.Selector) { 242 | s.Where(sql.GT(s.C(FieldType), v)) 243 | }) 244 | } 245 | 246 | // TypeGTE applies the GTE predicate on the "type" field. 247 | func TypeGTE(v int8) predicate.Pet { 248 | return predicate.Pet(func(s *sql.Selector) { 249 | s.Where(sql.GTE(s.C(FieldType), v)) 250 | }) 251 | } 252 | 253 | // TypeLT applies the LT predicate on the "type" field. 254 | func TypeLT(v int8) predicate.Pet { 255 | return predicate.Pet(func(s *sql.Selector) { 256 | s.Where(sql.LT(s.C(FieldType), v)) 257 | }) 258 | } 259 | 260 | // TypeLTE applies the LTE predicate on the "type" field. 261 | func TypeLTE(v int8) predicate.Pet { 262 | return predicate.Pet(func(s *sql.Selector) { 263 | s.Where(sql.LTE(s.C(FieldType), v)) 264 | }) 265 | } 266 | 267 | // CreateAtEQ applies the EQ predicate on the "create_at" field. 268 | func CreateAtEQ(v time.Time) predicate.Pet { 269 | return predicate.Pet(func(s *sql.Selector) { 270 | s.Where(sql.EQ(s.C(FieldCreateAt), v)) 271 | }) 272 | } 273 | 274 | // CreateAtNEQ applies the NEQ predicate on the "create_at" field. 275 | func CreateAtNEQ(v time.Time) predicate.Pet { 276 | return predicate.Pet(func(s *sql.Selector) { 277 | s.Where(sql.NEQ(s.C(FieldCreateAt), v)) 278 | }) 279 | } 280 | 281 | // CreateAtIn applies the In predicate on the "create_at" field. 282 | func CreateAtIn(vs ...time.Time) predicate.Pet { 283 | v := make([]interface{}, len(vs)) 284 | for i := range v { 285 | v[i] = vs[i] 286 | } 287 | return predicate.Pet(func(s *sql.Selector) { 288 | s.Where(sql.In(s.C(FieldCreateAt), v...)) 289 | }) 290 | } 291 | 292 | // CreateAtNotIn applies the NotIn predicate on the "create_at" field. 293 | func CreateAtNotIn(vs ...time.Time) predicate.Pet { 294 | v := make([]interface{}, len(vs)) 295 | for i := range v { 296 | v[i] = vs[i] 297 | } 298 | return predicate.Pet(func(s *sql.Selector) { 299 | s.Where(sql.NotIn(s.C(FieldCreateAt), v...)) 300 | }) 301 | } 302 | 303 | // CreateAtGT applies the GT predicate on the "create_at" field. 304 | func CreateAtGT(v time.Time) predicate.Pet { 305 | return predicate.Pet(func(s *sql.Selector) { 306 | s.Where(sql.GT(s.C(FieldCreateAt), v)) 307 | }) 308 | } 309 | 310 | // CreateAtGTE applies the GTE predicate on the "create_at" field. 311 | func CreateAtGTE(v time.Time) predicate.Pet { 312 | return predicate.Pet(func(s *sql.Selector) { 313 | s.Where(sql.GTE(s.C(FieldCreateAt), v)) 314 | }) 315 | } 316 | 317 | // CreateAtLT applies the LT predicate on the "create_at" field. 318 | func CreateAtLT(v time.Time) predicate.Pet { 319 | return predicate.Pet(func(s *sql.Selector) { 320 | s.Where(sql.LT(s.C(FieldCreateAt), v)) 321 | }) 322 | } 323 | 324 | // CreateAtLTE applies the LTE predicate on the "create_at" field. 325 | func CreateAtLTE(v time.Time) predicate.Pet { 326 | return predicate.Pet(func(s *sql.Selector) { 327 | s.Where(sql.LTE(s.C(FieldCreateAt), v)) 328 | }) 329 | } 330 | 331 | // And groups predicates with the AND operator between them. 332 | func And(predicates ...predicate.Pet) predicate.Pet { 333 | return predicate.Pet(func(s *sql.Selector) { 334 | s1 := s.Clone().SetP(nil) 335 | for _, p := range predicates { 336 | p(s1) 337 | } 338 | s.Where(s1.P()) 339 | }) 340 | } 341 | 342 | // Or groups predicates with the OR operator between them. 343 | func Or(predicates ...predicate.Pet) predicate.Pet { 344 | return predicate.Pet(func(s *sql.Selector) { 345 | s1 := s.Clone().SetP(nil) 346 | for i, p := range predicates { 347 | if i > 0 { 348 | s1.Or() 349 | } 350 | p(s1) 351 | } 352 | s.Where(s1.P()) 353 | }) 354 | } 355 | 356 | // Not applies the not operator on the given predicate. 357 | func Not(p predicate.Pet) predicate.Pet { 358 | return predicate.Pet(func(s *sql.Selector) { 359 | p(s.Not()) 360 | }) 361 | } 362 | --------------------------------------------------------------------------------