├── cover └── .keep ├── envs ├── common.env └── api.env ├── logger ├── go.mod ├── logger.go └── go.sum ├── util ├── ctxhelper │ ├── go.mod │ ├── ctxhelper_test.go │ ├── ctxhelper.coverprofile │ ├── ctxhelper.go │ └── go.sum ├── userinfo │ ├── go.mod │ └── userinfo.go └── go.mod ├── protos ├── types │ ├── go.mod │ ├── field_mask.proto │ ├── enums.proto │ ├── types.graphql.go │ ├── go.sum │ └── enums.pb.go ├── google │ └── api │ │ ├── annotation.proto │ │ ├── httpbody.proto │ │ ├── field_behaviour.proto │ │ └── field_mask.proto └── include │ └── graphql.proto ├── .idea ├── vcs.xml ├── .gitignore ├── modules.xml └── platform.iml ├── config ├── fx.go ├── go.mod └── config.go ├── servers ├── prometheusServer │ ├── fx.go │ ├── prometheus.yml │ └── server.go ├── cleanup │ ├── fx.go │ └── cleanup.go ├── openTracing │ └── tracer │ │ └── jaeger │ │ ├── fx.go │ │ └── jaeger.go ├── grpc │ ├── fx.go │ ├── middleware │ │ ├── public_endpoint.go │ │ ├── interceptors.go │ │ └── auth.go │ ├── go.mod │ ├── register.go │ ├── server.go │ └── grpcErrors │ │ └── grpcErrors.go ├── graphql │ ├── middleware │ │ ├── ctx-change.go │ │ ├── cors.go │ │ ├── logger.go │ │ └── request-id.go │ ├── register.go │ └── server.go ├── healthcheck │ ├── health-check.go │ └── db │ │ └── db.go ├── rest │ ├── middleware │ │ ├── cors.go │ │ ├── request-id.go │ │ └── logger.go │ ├── register.go │ └── server.go └── metrics │ ├── manager.go │ └── metrics.go ├── client └── grpcClient │ ├── fx.go │ └── client.go ├── Dockerfile ├── db ├── fx.go ├── go.mod └── connection.go ├── modules ├── authentication │ └── v1 │ │ ├── generate.go │ │ ├── external-svc │ │ ├── logout_test.go │ │ ├── login.go │ │ ├── login_test.go │ │ ├── logout.go │ │ ├── login_callback_test.go │ │ ├── external_suite_test.go │ │ ├── service.go │ │ └── login_callback.go │ │ └── pb │ │ ├── authentication.proto │ │ └── pb.graphql.go └── user-profile │ └── v1 │ ├── generate.go │ ├── external-svc │ ├── service.go │ ├── get_user_profile_by_sub.go │ ├── get_user_profile.go │ ├── get_user_profile_test.go │ ├── get_user_profile_by_sub_test.go │ └── external_suite_test.go │ ├── internal-svc │ ├── service.go │ ├── update_user_profile.go │ ├── delete_user_profile.go │ ├── create_user_profile.go │ ├── delete_user_profile_test.go │ ├── update_user_profile_test.go │ ├── internal_suite_test.go │ └── create_user_profile_test.go │ ├── models │ └── user_profile.go │ └── pb │ ├── user_profile_int.proto │ ├── user_profile.proto │ ├── user_profile_grpc.pb.go │ ├── user_profile_int_grpc.pb.go │ ├── user_profile_int.pb.validate.go │ └── user_profile.pb.validate.go ├── .editorconfig ├── cmd └── api │ ├── main.go │ ├── app.go │ ├── provider.go │ └── invoker.go ├── .gitignore ├── telementry └── prometheus │ └── prometheus.yml ├── docker-compose.yml ├── Makefile ├── LICENSE ├── config.yml ├── .github └── workflows │ └── go.yml ├── go.mod └── README.md /cover/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /envs/common.env: -------------------------------------------------------------------------------- 1 | CONFIG_NAME=config 2 | CONFIG_DIRECTORY=/app/ 3 | -------------------------------------------------------------------------------- /logger/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/deqode/GoArcc/logger 2 | 3 | go 1.16 4 | 5 | require go.uber.org/zap v1.18.1 6 | -------------------------------------------------------------------------------- /util/ctxhelper/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/deqode/GoArcc/util/ctxhelper 2 | 3 | go 1.16 4 | 5 | require go.uber.org/zap v1.18.1 6 | -------------------------------------------------------------------------------- /envs/api.env: -------------------------------------------------------------------------------- 1 | POSTGRES_HOST=postgres 2 | POSTGRES_PORT=5432 3 | POSTGRES_USER=postgres 4 | POSTGRES_PASSWORD=postgres 5 | POSTGRES_DB_NAME=postgres 6 | -------------------------------------------------------------------------------- /util/userinfo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/deqode/GoArcc/util/userinfo 2 | 3 | go 1.16 4 | 5 | require ( 6 | google.golang.org/grpc v1.39.0 7 | gorm.io/gorm v1.21.11 8 | ) 9 | -------------------------------------------------------------------------------- /protos/types/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/deqode/GoArcc/protos/types 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/graphql-go/graphql v0.7.9 7 | google.golang.org/protobuf v1.27.1 8 | ) 9 | -------------------------------------------------------------------------------- /util/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/deqode/GoArcc/util 2 | 3 | go 1.16 4 | 5 | require ( 6 | go.uber.org/zap v1.18.1 7 | google.golang.org/grpc v1.39.0 8 | gorm.io/gorm v1.21.11 9 | ) 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /config/fx.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "go.uber.org/fx" 4 | 5 | // ProviderFx ConfigProviderFx : Provider of GetConfig 6 | var ProviderFx = fx.Options( 7 | fx.Provide( 8 | GetConfig, 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /servers/prometheusServer/fx.go: -------------------------------------------------------------------------------- 1 | package prometheusServer 2 | 3 | /*import "go.uber.org/fx" 4 | 5 | var InitPromthesiusServerFx = fx.Options( 6 | fx.Provide( 7 | InitPromthesiusServer, 8 | ), 9 | ) 10 | */ 11 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /protos/types/field_mask.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package goarcc.types; 4 | 5 | option go_package = "github.com/deqode/GoArcc/protos/types"; 6 | 7 | message FieldMask { 8 | repeated string field_mask = 1; 9 | } 10 | -------------------------------------------------------------------------------- /servers/cleanup/fx.go: -------------------------------------------------------------------------------- 1 | package cleanup 2 | 3 | import "go.uber.org/fx" 4 | 5 | // CleanupFx will provide the constructor method GetCleanupConfig. 6 | var CleanupFx = fx.Options( 7 | fx.Provide( 8 | GetCleanupConfig, 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /servers/openTracing/tracer/jaeger/fx.go: -------------------------------------------------------------------------------- 1 | package jaeger 2 | 3 | import "go.uber.org/fx" 4 | 5 | // JaegerTracerFx : Constructor method for InitJaeger. 6 | var JaegerTracerFx = fx.Options( 7 | fx.Provide( 8 | InitJaeger, 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /client/grpcClient/fx.go: -------------------------------------------------------------------------------- 1 | package grpcClient 2 | 3 | import "go.uber.org/fx" 4 | 5 | // ConnectionFx GrpcClientConnectionFx : Provider of GetGrpcClientConnection 6 | var ConnectionFx = fx.Options( 7 | fx.Provide( 8 | GetGrpcClientConnection, 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /servers/grpc/fx.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import "go.uber.org/fx" 4 | 5 | // InitGrpcBeforeServingFx : constructor for InitGrpcBeforeServing method. 6 | var InitGrpcBeforeServingFx = fx.Options( 7 | fx.Provide( 8 | InitGrpcBeforeServing, 9 | ), 10 | ) 11 | -------------------------------------------------------------------------------- /util/ctxhelper/ctxhelper_test.go: -------------------------------------------------------------------------------- 1 | package ctxhelper 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestContext(t *testing.T) { 10 | ctx := NewContextComponentName(context.Background(), "Shivang") 11 | fmt.Println(ComponentNameFromContext(ctx)) 12 | } 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.16.4-stretch 2 | 3 | #ENV GO111MODULE=on go get program@latest 4 | 5 | #RUN go get -u golang.org/x/tools/ 6 | 7 | WORKDIR /app 8 | 9 | #COPY go.mod . 10 | #COPY go.sum . 11 | # 12 | #RUN go mod download 13 | # 14 | #COPY . . 15 | # 16 | #RUN make build-api 17 | 18 | #RUN ls bin 19 | -------------------------------------------------------------------------------- /.idea/platform.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /config/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/deqode/GoArcc/config 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/spf13/viper v1.8.1 7 | go.uber.org/fx v1.13.1 8 | go.uber.org/zap v1.18.1 9 | github.com/deqode/GoArcc/logger v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | replace github.com/deqode/GoArcc/logger => ./../logger 13 | -------------------------------------------------------------------------------- /db/fx.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "go.uber.org/fx" 4 | 5 | // DatabaseConnectionFx : constructor provide a initialised db connection with the given port. 6 | // No need to close the connection of the db because we have a single pool with single db instance. 7 | var DatabaseConnectionFx = fx.Options( 8 | fx.Provide( 9 | NewConnection, 10 | ), 11 | ) 12 | -------------------------------------------------------------------------------- /protos/types/enums.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package goarcc.types; 4 | 5 | option go_package = "github.com/deqode/GoArcc/protos/types"; 6 | 7 | enum VCSProviders { 8 | UNKNOWN = 0; 9 | GITHUB = 1; 10 | GITLAB = 2; 11 | BITBUCKET = 3; 12 | } 13 | 14 | /* 15 | protoc -I ./ --go_out=paths=source_relative:. --graphql_out=paths=source_relative:. enums.proto && goimports -w . 16 | */ 17 | -------------------------------------------------------------------------------- /modules/authentication/v1/generate.go: -------------------------------------------------------------------------------- 1 | package authentication 2 | 3 | // - protoc plugins must be in $PATH 4 | 5 | //generate from authentication.proto 6 | //go:generate bash -e -o pipefail -c "protoc -I . --proto_path=../../../protos --grpc-gateway_out=./ --grpc-gateway_opt logtostderr=true --go_out=./ --go-grpc_out=./ --go-grpc_opt=require_unimplemented_servers=false --graphql_out=./ --validate_out='lang=go:./' ./pb/authentication.proto" 7 | -------------------------------------------------------------------------------- /modules/authentication/v1/external-svc/logout_test.go: -------------------------------------------------------------------------------- 1 | package external_svc_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | ) 6 | 7 | var _ = Describe("Logout Test", func() { 8 | Describe("Logout Test", func() { 9 | By("By Rpc Calls") 10 | 11 | Context("Get an error when auth0 domain is not present or empty", func() { 12 | It("Failed precondition err must be responded", func() { 13 | 14 | }) 15 | }) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /servers/prometheusServer/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 10s 3 | evaluation_interval: 10s 4 | 5 | scrape_configs: 6 | - job_name: 'prometheus' 7 | static_configs: 8 | - targets: ['localhost:9090'] 9 | 10 | - job_name: 'system' 11 | static_configs: 12 | - targets: ['localhost:9100'] 13 | 14 | - job_name: 'Alfred Monitoring' 15 | static_configs: 16 | - targets: ['localhost:7070'] 17 | -------------------------------------------------------------------------------- /servers/grpc/middleware/public_endpoint.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | // TODO: Priority zero, validate if these are available are registered, otherwise fail on startup 4 | var publicEndpoint = []string{ 5 | "/goarcc.vcs_connection.v1.VCSConnections/ListAllSupportedVCSProviders", 6 | "/goarcc.authentication.v1.Authentications/Login", 7 | "/goarcc.authentication.v1.Authentications/LoginCallback", 8 | "/goarcc.user_profile.v1.user-profile/GetUserProfileBySub", 9 | } 10 | -------------------------------------------------------------------------------- /db/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/deqode/GoArcc/db 2 | 3 | go 1.16 4 | 5 | require ( 6 | go.uber.org/fx v1.13.1 7 | go.uber.org/zap v1.18.1 8 | github.com/deqode/GoArcc/config v0.0.0-00010101000000-000000000000 9 | github.com/deqode/GoArcc/logger v0.0.0-00010101000000-000000000000 10 | gorm.io/driver/postgres v1.1.0 11 | gorm.io/gorm v1.21.11 12 | ) 13 | 14 | replace ( 15 | github.com/deqode/GoArcc/config => ./../config 16 | github.com/deqode/GoArcc/logger => ./../logger 17 | ) 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; indicate this is the root of the project 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | 7 | end_of_line = LF 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | indent_style = space 12 | indent_size = 4 13 | 14 | [Makefile] 15 | indent_style = tab 16 | 17 | [makefile] 18 | indent_style = tab 19 | 20 | [*.go] 21 | indent_style = tab 22 | 23 | [*.yaml] 24 | indent_style = space 25 | indent_size = 2 26 | 27 | [*.yml] 28 | indent_style = space 29 | indent_size = 2 30 | -------------------------------------------------------------------------------- /cmd/api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/deqode/GoArcc/logger" 5 | "go.uber.org/zap" 6 | ) 7 | 8 | //todo : Alarm !!!!!! Do not touch the invocation sequence, either you might go through sleepless nights 9 | // main : entry point 10 | func main() { 11 | //logger initialize before app starts because in provider we need logger 12 | logger.Init(logger.Config{ 13 | LogLevel: zap.DebugLevel, // TODO: Take this level from config 14 | Development: false, 15 | }) 16 | GetApp().Run() 17 | } 18 | -------------------------------------------------------------------------------- /cmd/api/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "go.uber.org/fx" 4 | 5 | //GetApp : Get App will return the fx app. 6 | // Fx App contains invokers , providers , lifecycles etc. 7 | // When we start the application using fx app then used providers will initialises first. 8 | // After that invoker will invoked automatically. 9 | // Note: Invokers will be executed in the same order. 10 | func GetApp() *fx.App { 11 | opts := make([]fx.Option, 0) 12 | opts = GetProviderOptions() 13 | opts = append(opts, GetInvokersOptions()) 14 | return fx.New( 15 | opts..., 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /util/ctxhelper/ctxhelper.coverprofile: -------------------------------------------------------------------------------- 1 | mode: atomic 2 | github.com/deqode/GoArcc/util/ctxhelper/ctxhelper.go:20.89,22.2 1 1 3 | github.com/deqode/GoArcc/util/ctxhelper/ctxhelper.go:25.84,28.2 2 1 4 | github.com/deqode/GoArcc/util/ctxhelper/ctxhelper.go:32.80,34.2 1 0 5 | github.com/deqode/GoArcc/util/ctxhelper/ctxhelper.go:37.75,40.2 2 0 6 | github.com/deqode/GoArcc/util/ctxhelper/ctxhelper.go:44.74,46.2 1 0 7 | github.com/deqode/GoArcc/util/ctxhelper/ctxhelper.go:49.69,52.2 2 0 8 | github.com/deqode/GoArcc/util/ctxhelper/ctxhelper.go:56.80,58.2 1 0 9 | github.com/deqode/GoArcc/util/ctxhelper/ctxhelper.go:61.75,64.2 2 0 10 | -------------------------------------------------------------------------------- /servers/graphql/middleware/ctx-change.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "google.golang.org/grpc/metadata" 5 | "net/http" 6 | ) 7 | 8 | // ChangeContext : ChangeContext is responsible to add the authorization token to the context so that 9 | // in grpc server we can easily get authentication token and verify the authentication token. 10 | func ChangeContext(h http.Handler) http.Handler { 11 | return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { 12 | request = request.WithContext(metadata.AppendToOutgoingContext(request.Context(), "Authorization", request.Header.Get("Authorization"))) 13 | h.ServeHTTP(writer, request) 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /modules/user-profile/v1/generate.go: -------------------------------------------------------------------------------- 1 | package user_profile 2 | 3 | // generate from pb/user_profile.proto 4 | //go:generate bash -e -o pipefail -c "protoc -I . --proto_path=../../../protos --grpc-gateway_out=./ --grpc-gateway_opt logtostderr=true --go_out=./ --go-grpc_out=./ --go-grpc_opt=require_unimplemented_servers=false --graphql_out=./ --validate_out='lang=go:./' ./pb/user_profile.proto" 5 | 6 | // generate from pb/user_profile_int.proto 7 | //go:generate bash -e -o pipefail -c "protoc -I . --proto_path=../../../protos --proto_path=./pb --grpc-gateway_out=./ --grpc-gateway_opt logtostderr=true --go_out=./ --go-grpc_out=./ --go-grpc_opt=require_unimplemented_servers=false --validate_out='lang=go:./' ./pb/user_profile_int.proto" 8 | -------------------------------------------------------------------------------- /cmd/api/provider.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/deqode/GoArcc/client/grpcClient" 5 | "github.com/deqode/GoArcc/config" 6 | "github.com/deqode/GoArcc/db" 7 | "github.com/deqode/GoArcc/servers/cleanup" 8 | "github.com/deqode/GoArcc/servers/grpc" 9 | "github.com/deqode/GoArcc/servers/openTracing/tracer/jaeger" 10 | "go.uber.org/fx" 11 | ) 12 | 13 | // GetProviderOptions ProviderOptions: Global Constructor. 14 | // Sequence in fx does not matter, So you can write in any order you want. 15 | func GetProviderOptions() []fx.Option { 16 | return []fx.Option{ 17 | config.ProviderFx, 18 | grpc.InitGrpcBeforeServingFx, 19 | db.DatabaseConnectionFx, 20 | cleanup.CleanupFx, 21 | jaeger.JaegerTracerFx, 22 | grpcClient.ConnectionFx, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The file containing environment variables 2 | .env 3 | ### Go template 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Ignore IDE specific files 18 | .idea/ 19 | .vscode/ 20 | 21 | #ignore build file 22 | goarcc 23 | go_build_goarcc 24 | go_build_platform 25 | go_build_backend_ 26 | cmd/api/repositories/ 27 | cmd/api/repositories 28 | vendor 29 | 30 | # temporary remove for production 31 | ./tests 32 | 33 | 34 | #test file 35 | coverage.out 36 | coverage.html 37 | .cover 38 | #cover/ 39 | 40 | # Build artifacts 41 | bin/ 42 | 43 | cache 44 | .docker/ 45 | -------------------------------------------------------------------------------- /telementry/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s # By default, scrape targets every 15 seconds. 3 | 4 | # Attach these labels to any time series or alerts when communicating with 5 | # external systems (federation, remote storage, Alertmanager). 6 | external_labels: 7 | monitor: 'codelab-monitor' 8 | 9 | # A scrape configuration containing exactly one endpoint to scrape: 10 | # Here it's Prometheus itself. 11 | scrape_configs: 12 | # The job name is added as a label `job=` to any timeseries scraped from this config. 13 | - job_name: 'prometheus' 14 | 15 | # Override the global default and scrape targets from this job every 5 seconds. 16 | scrape_interval: 5s 17 | 18 | static_configs: 19 | - targets: ['localhost:9090'] 20 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | 3 | volumes: 4 | postgres: 5 | 6 | services: 7 | 8 | backend_api: 9 | image: golang:1.16.4-stretch 10 | working_dir: /app 11 | volumes: 12 | - .:/app 13 | - /tmp/docker_cache/go_mod:/go/pkg/mod 14 | env_file: 15 | - envs/common.env 16 | - envs/api.env 17 | command: bin/goarcc 18 | depends_on: 19 | - postgres 20 | 21 | jaeger: 22 | image: jaegertracing/all-in-one:latest 23 | ports: 24 | - "6831:6831/udp" 25 | - "16686:16686" 26 | 27 | postgres: 28 | image: postgres 29 | environment: 30 | POSTGRES_DB: postgres 31 | POSTGRES_USER: postgres 32 | POSTGRES_PASSWORD: postgres 33 | PGDATA: /data/postgres 34 | volumes: 35 | - postgres:/data/postgres 36 | -------------------------------------------------------------------------------- /servers/graphql/register.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "github.com/deqode/GoArcc/logger" 5 | userProfilePb "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 6 | "github.com/ysugimoto/grpc-graphql-gateway/runtime" 7 | "go.uber.org/zap" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | // RegisterGraphqlModules Todo : Whenever any new modules will be in goarcc : it must be registered in below method 12 | /* 13 | RegisterGraphqlModules: Mapping the services with the single graphql endpoint 14 | */ 15 | func RegisterGraphqlModules(mux *runtime.ServeMux, conn *grpc.ClientConn) error { 16 | if err := userProfilePb.RegisterUserProfilesGraphqlHandler(mux, conn); err != nil { 17 | logger.Log.Fatal("failed to start HTTP gateway", zap.String("reason", err.Error())) 18 | return err 19 | } 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /modules/user-profile/v1/external-svc/service.go: -------------------------------------------------------------------------------- 1 | package external_svc 2 | 3 | import ( 4 | "github.com/deqode/GoArcc/config" 5 | model "github.com/deqode/GoArcc/modules/user-profile/v1/models" 6 | "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 7 | "google.golang.org/grpc" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | type userProfilesServer struct { 12 | db *gorm.DB 13 | config *config.Config 14 | grpcClient *grpc.ClientConn 15 | } 16 | 17 | func NewUserProfilesServer( 18 | db *gorm.DB, 19 | config *config.Config, 20 | grpcClientConn *grpc.ClientConn, 21 | ) pb.UserProfilesServer { 22 | 23 | //initial migration of databases: schema migration 24 | model.InitialMigrationUserProfile(db) 25 | return &userProfilesServer{ 26 | db: db, 27 | config: config, 28 | grpcClient: grpcClientConn, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /modules/authentication/v1/external-svc/login.go: -------------------------------------------------------------------------------- 1 | package external_svc 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "encoding/base64" 7 | "github.com/deqode/GoArcc/modules/authentication/v1/pb" 8 | "github.com/golang/protobuf/ptypes/empty" 9 | "google.golang.org/grpc/codes" 10 | "google.golang.org/grpc/status" 11 | ) 12 | 13 | func (s *authenticationServer) Login(context.Context, *empty.Empty) (*pb.LoginResponse, error) { 14 | b := make([]byte, 32) 15 | _, err := rand.Read(b) 16 | if err != nil { 17 | return nil, err 18 | } 19 | state := base64.StdEncoding.EncodeToString(b) 20 | if s.authenticator == nil { 21 | return nil, status.Error(codes.Internal, "Authenticator not initialised") 22 | } 23 | url := s.authenticator.Config.AuthCodeURL(state) 24 | 25 | return &pb.LoginResponse{ 26 | Url: url, 27 | }, nil 28 | } 29 | -------------------------------------------------------------------------------- /servers/grpc/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/deqode/GoArcc/servers/grpc 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/go-redis/redis/v8 v8.11.0 7 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 8 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 9 | github.com/justinas/alice v1.2.0 10 | github.com/opentracing/opentracing-go v1.2.0 11 | github.com/pkg/errors v0.9.1 12 | github.com/prometheus/client_golang v1.11.0 13 | go.uber.org/fx v1.13.1 14 | go.uber.org/zap v1.18.1 15 | github.com/deqode/GoArcc/config v0.0.0-00010101000000-000000000000 16 | github.com/deqode/GoArcc/logger v0.0.0-00010101000000-000000000000 17 | google.golang.org/grpc v1.39.0 18 | gopkg.in/square/go-jose.v2 v2.6.0 19 | gorm.io/gorm v1.21.11 20 | ) 21 | 22 | replace ( 23 | github.com/deqode/GoArcc/config => ./../../config 24 | github.com/deqode/GoArcc/logger => ./../../logger 25 | ) 26 | -------------------------------------------------------------------------------- /modules/user-profile/v1/internal-svc/service.go: -------------------------------------------------------------------------------- 1 | package internal_svc 2 | 3 | import ( 4 | "github.com/deqode/GoArcc/config" 5 | model "github.com/deqode/GoArcc/modules/user-profile/v1/models" 6 | "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 7 | "google.golang.org/grpc" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | // Internal Service Configuration 12 | type userProfileInServer struct { 13 | db *gorm.DB 14 | config *config.Config 15 | grpcClient *grpc.ClientConn 16 | } 17 | 18 | func NewUserProfileInServer( 19 | db *gorm.DB, 20 | config *config.Config, 21 | grpcClientConn *grpc.ClientConn, 22 | ) pb.UserProfileInternalServer { 23 | //initial migration of databases: schema migration 24 | model.InitialMigrationUserProfile(db) 25 | return &userProfileInServer{ 26 | db: db, 27 | config: config, 28 | grpcClient: grpcClientConn, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /modules/user-profile/v1/internal-svc/update_user_profile.go: -------------------------------------------------------------------------------- 1 | package internal_svc 2 | 3 | import ( 4 | "context" 5 | "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 6 | "google.golang.org/grpc/codes" 7 | "google.golang.org/grpc/status" 8 | ) 9 | 10 | func (s *userProfileInServer) UpdateUserProfile(ctx context.Context, in *pb.UpdateUserProfileRequest) (*pb.UserProfile, error) { 11 | if in.GetUserProfile() == nil { 12 | return nil, status.Error(codes.FailedPrecondition, "UserProfile to update is not provided") 13 | } 14 | if err := in.Validate(); err != nil { 15 | return nil, err 16 | } 17 | return &pb.UserProfile{ 18 | Id: "", 19 | Sub: "", 20 | Name: "", 21 | UserName: "", 22 | Email: "", 23 | PhoneNumber: "", 24 | ExternalSource: 0, 25 | ProfilePicUrl: "", 26 | TokenValidTill: nil, 27 | }, nil 28 | } 29 | -------------------------------------------------------------------------------- /modules/authentication/v1/external-svc/login_test.go: -------------------------------------------------------------------------------- 1 | package external_svc_test 2 | 3 | import ( 4 | "context" 5 | "github.com/deqode/GoArcc/modules/authentication/v1/pb" 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("Login", func() { 11 | var ( 12 | authServer pb.AuthenticationsServer 13 | //authenticator *external_svc.Authenticator 14 | ) 15 | BeforeEach(func() { 16 | authServer = AuthServerTest 17 | //authenticator = AuthenticatorTest 18 | }) 19 | Describe("Getting External VCS login url", func() { 20 | By("By Rpc Calls") 21 | 22 | // positive test case 23 | Context("Get an error if returned url is empty", func() { 24 | It("Failed precondition err must be responded", func() { 25 | res, err := authServer.Login(context.Background(), nil) 26 | Expect(err).Should(BeNil()) 27 | Expect(res).ShouldNot(BeNil()) 28 | }) 29 | }) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /protos/types/types.graphql.go: -------------------------------------------------------------------------------- 1 | // Code generated by proroc-gen-graphql, DO NOT EDIT. 2 | package types 3 | 4 | import ( 5 | "github.com/graphql-go/graphql" 6 | ) 7 | 8 | var ( 9 | gql__enum_VCSProviders *graphql.Enum // enum VCSProviders in enums.proto 10 | ) 11 | 12 | func Gql__enum_VCSProviders() *graphql.Enum { 13 | if gql__enum_VCSProviders == nil { 14 | gql__enum_VCSProviders = graphql.NewEnum(graphql.EnumConfig{ 15 | Name: "Types_Enum_VCSProviders", 16 | Values: graphql.EnumValueConfigMap{ 17 | "UNKNOWN": &graphql.EnumValueConfig{ 18 | Value: VCSProviders(0), 19 | }, 20 | "GITHUB": &graphql.EnumValueConfig{ 21 | Value: VCSProviders(1), 22 | }, 23 | "GITLAB": &graphql.EnumValueConfig{ 24 | Value: VCSProviders(2), 25 | }, 26 | "BITBUCKET": &graphql.EnumValueConfig{ 27 | Value: VCSProviders(3), 28 | }, 29 | }, 30 | }) 31 | } 32 | return gql__enum_VCSProviders 33 | } 34 | -------------------------------------------------------------------------------- /protos/types/go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 2 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 3 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 4 | github.com/graphql-go/graphql v0.7.9 h1:5Va/Rt4l5g3YjwDnid3vFfn43faaQBq7rMcIZ0VnV34= 5 | github.com/graphql-go/graphql v0.7.9/go.mod h1:k6yrAYQaSP59DC5UVxbgxESlmVyojThKdORUqGDGmrI= 6 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 7 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 8 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 9 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 10 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 11 | -------------------------------------------------------------------------------- /modules/user-profile/v1/internal-svc/delete_user_profile.go: -------------------------------------------------------------------------------- 1 | package internal_svc 2 | 3 | import ( 4 | "context" 5 | model "github.com/deqode/GoArcc/modules/user-profile/v1/models" 6 | "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 7 | "github.com/golang/protobuf/ptypes/empty" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/status" 10 | ) 11 | 12 | func (s *userProfileInServer) DeleteUserProfile(ctx context.Context, in *pb.DeleteUserProfileRequest) (*empty.Empty, error) { 13 | //request validation 14 | if in == nil { 15 | return nil, status.Error(codes.FailedPrecondition, "Request can't be nil") 16 | } 17 | if err := in.Validate(); err != nil { 18 | return nil, err 19 | } 20 | //by default it will delete with primary key. 21 | // ie: Delete From Account where id = in.id 22 | tx := s.db.Where("id = ?", in.Id).Delete(&model.UserProfile{}) 23 | if tx.Error != nil { 24 | return nil, tx.Error 25 | } 26 | return &empty.Empty{}, nil 27 | } 28 | -------------------------------------------------------------------------------- /servers/healthcheck/health-check.go: -------------------------------------------------------------------------------- 1 | package healthcheck 2 | 3 | import ( 4 | "github.com/deqode/GoArcc/config" 5 | postgreshealth "github.com/deqode/GoArcc/servers/healthcheck/db" 6 | "github.com/dimiro1/health" 7 | "gorm.io/gorm" 8 | "net/http" 9 | ) 10 | 11 | // RunHealthCheckServer : will start the health check server 12 | func RunHealthCheckServer(config *config.Config, db *gorm.DB) error { 13 | 14 | //postgres health check 15 | postgresql := postgreshealth.NewPostgresChecker(db) 16 | handler := health.NewHandler() 17 | postgresql.Check() 18 | handler.AddChecker("Postgres", postgresql) 19 | http.Handle("/health/", handler) 20 | return http.ListenAndServe(config.HealthCheck.Host+":"+config.HealthCheck.Port, nil) 21 | } 22 | 23 | // HealthCheckRunner : will responsible to run health check server in non blocking way. 24 | func HealthCheckRunner(config *config.Config, db *gorm.DB) error { 25 | go func() { 26 | _ = RunHealthCheckServer(config, db) 27 | }() 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /servers/rest/middleware/cors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import "net/http" 4 | 5 | //setupCorsResponse : is the helper method which is responsible to set the header in the response on every request. 6 | func setupCorsResponse(w *http.ResponseWriter, req *http.Request) { 7 | (*w).Header().Set("Access-Control-Allow-Origin", "*") 8 | (*w).Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, PATCH") 9 | (*w).Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") 10 | } 11 | 12 | // AddCors : AddCors is the middleware which will add the cors in the response and response 13 | // with status ok on preflight request. 14 | func AddCors(h http.Handler) http.Handler { 15 | return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { 16 | setupCorsResponse(&writer, request) 17 | if request.Method == "OPTIONS" { 18 | writer.WriteHeader(http.StatusOK) 19 | } 20 | h.ServeHTTP(writer, request) 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /servers/metrics/manager.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "context" 5 | "github.com/deqode/GoArcc/servers/grpc/grpcErrors" 6 | "google.golang.org/grpc" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | // Manager Manager 12 | type Manager struct { 13 | metrics Metrics 14 | } 15 | 16 | // NewMetricsManager InterceptorManager constructor 17 | func NewMetricsManager(metrics Metrics) *Manager { 18 | return &Manager{metrics: metrics} 19 | } 20 | 21 | // Metrics : 22 | func (im *Manager) Metrics(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 23 | start := time.Now() 24 | resp, err := handler(ctx, req) 25 | var status = http.StatusOK 26 | if err != nil { 27 | status = grpcErrors.MapGRPCErrCodeToHTTPStatus(grpcErrors.ParseGRPCErrStatusCode(err)) 28 | } 29 | im.metrics.ObserveResponseTime(status, info.FullMethod, info.FullMethod, time.Since(start).Seconds()) 30 | im.metrics.IncHits(status, info.FullMethod, info.FullMethod) 31 | 32 | return resp, err 33 | } 34 | -------------------------------------------------------------------------------- /modules/authentication/v1/external-svc/logout.go: -------------------------------------------------------------------------------- 1 | package external_svc 2 | 3 | import ( 4 | "context" 5 | "github.com/golang/protobuf/ptypes/empty" 6 | "net/url" 7 | ) 8 | 9 | func (s *authenticationServer) Logout(context.Context, *empty.Empty) (*empty.Empty, error) { 10 | domain := s.config.Auth.Auth0Domain 11 | 12 | logoutURL, err := url.Parse("http://" + domain) 13 | 14 | if err != nil { 15 | //http.Error(w, err.Error(), http.StatusInternalServerError) 16 | return nil, err 17 | } 18 | 19 | logoutURL.Path += "/v1/authenticationServer/logout" 20 | parameters := url.Values{} 21 | 22 | var scheme string 23 | scheme = "http" 24 | 25 | returnTo, err := url.Parse(scheme + "://" + "http://localhost:8082") 26 | if err != nil { 27 | //http.Error(w, err.Error(), http.StatusInternalServerError) 28 | return nil, err 29 | } 30 | parameters.Add("returnTo", returnTo.String()) 31 | parameters.Add("client_id", s.config.Auth.Auth0ClientID) 32 | logoutURL.RawQuery = parameters.Encode() 33 | 34 | return &empty.Empty{}, nil 35 | } 36 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | get-linter: 2 | go get github.com/mgechev/revive 3 | 4 | get-richgo: 5 | go get -u github.com/kyoh86/richgo 6 | 7 | get-dupl: 8 | go get -u github.com/mibk/dupl 9 | 10 | get-errcheck: 11 | go get -u github.com/kisielk/errcheck 12 | 13 | get-gocritic: 14 | go get -u github.com/go-critic/go-critic/cmd/gocritic 15 | 16 | run-gocritic: 17 | gocritic check ./... 18 | 19 | run-errcheck: get-errcheck 20 | errcheck ./... 21 | 22 | run-dupl: get-dupl 23 | dupl */**.go 24 | 25 | 26 | 27 | run-linter: get-linter 28 | revive -formatter friendly ./... 29 | 30 | get-fmt: 31 | go get fmt 32 | 33 | run-fmt: 34 | go fmt ./... 35 | 36 | tidy: 37 | go mod tidy 38 | go mod vendor 39 | 40 | # Run all the linters 41 | run-all-linter: get-linter get-fmt run-linter run-fmt tidy 42 | 43 | #Get the test coverage 44 | test: get-richgo 45 | go test -cover ./... 46 | go test -coverprofile=./cover/coverage.out ./... 47 | cat cover/coverage.out 48 | go tool cover -html=./cover/coverage.out 49 | 50 | build: 51 | go build -o bin/goarcc -buildmode pie ./cmd/api 52 | -------------------------------------------------------------------------------- /servers/healthcheck/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/dimiro1/health" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | //Checker is a checker that check a given db 9 | type Checker struct { 10 | DB *gorm.DB 11 | } 12 | 13 | // NewPostgresChecker NewChecker returns a new url.Checker with the given URL 14 | func NewPostgresChecker(db *gorm.DB) Checker { 15 | return Checker{ 16 | DB: db, 17 | } 18 | } 19 | 20 | // Check execute queries in the database 21 | // The first is a simple one used to verify if the database is up 22 | // If is Up then another query is executed, querying for the database version 23 | func (c Checker) Check() health.Health { 24 | var ( 25 | version string 26 | ) 27 | 28 | h := health.NewHealth() 29 | 30 | if c.DB == nil { 31 | h.Down().AddInfo("error", "Empty resource") 32 | return h 33 | } 34 | 35 | tx := c.DB.Raw(`Select version()`).Scan(&version) 36 | if tx.Error != nil || version == "" { 37 | h.Down().AddInfo("error", tx.Error.Error()) 38 | return h 39 | } 40 | 41 | h.Up().AddInfo("version", version) 42 | 43 | return h 44 | } 45 | -------------------------------------------------------------------------------- /servers/graphql/middleware/cors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import "net/http" 4 | 5 | //setupCorsResponse : is the helper method which is responsible to set the header in the response on every request. 6 | // TODO: Instead of allowing "8" hardcoade, move this to config 7 | func setupCorsResponse(w *http.ResponseWriter, req *http.Request) { 8 | (*w).Header().Set("Access-Control-Allow-Origin", "*") 9 | (*w).Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, PATCH") 10 | (*w).Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") 11 | } 12 | 13 | // AddCors : AddCors is the middleware which will add the cors in the response and response 14 | // with status ok on preflight request. 15 | func AddCors(h http.Handler) http.Handler { 16 | return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { 17 | setupCorsResponse(&writer, request) 18 | if request.Method == "OPTIONS" { 19 | writer.WriteHeader(http.StatusOK) 20 | } 21 | h.ServeHTTP(writer, request) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /servers/grpc/register.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "github.com/deqode/GoArcc/config" 5 | authExt "github.com/deqode/GoArcc/modules/authentication/v1/external-svc" 6 | authPb "github.com/deqode/GoArcc/modules/authentication/v1/pb" 7 | userExt "github.com/deqode/GoArcc/modules/user-profile/v1/external-svc" 8 | userProfilePb "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 9 | "google.golang.org/grpc" 10 | "gorm.io/gorm" 11 | ) 12 | 13 | // RegisterGrpcModules Todo : Whenever any new modules will be in goarcc : it must be registered in below method 14 | /* 15 | RegisterGrpcModules: will register the modules/services to the server. 16 | */ 17 | func RegisterGrpcModules(srv *grpc.Server, 18 | db *gorm.DB, 19 | config *config.Config, 20 | grpcClientConnection *grpc.ClientConn) { 21 | 22 | //todo register new grpc modules here 23 | //register user modules 24 | userProfilePb.RegisterUserProfilesServer(srv, userExt.NewUserProfilesServer(db, config, grpcClientConnection)) 25 | authPb.RegisterAuthenticationsServer(srv, authExt.NewAuthenticationServer(db, config, grpcClientConnection)) 26 | } 27 | -------------------------------------------------------------------------------- /servers/rest/register.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "context" 5 | "github.com/deqode/GoArcc/logger" 6 | authPb "github.com/deqode/GoArcc/modules/authentication/v1/pb" 7 | userProfilePb "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 8 | "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 9 | "go.uber.org/zap" 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | // RegisterRESTModules Todo : Whenever any new modules will be in goarcc : it must be registered in below method 14 | //Todo: Remove local host from here 15 | func RegisterRESTModules(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { 16 | //opts := []grpc.DialOption{grpc.WithInsecure()} 17 | if err := userProfilePb.RegisterUserProfilesHandler(ctx, mux, conn); err != nil { 18 | logger.Log.Fatal("failed to start HTTP gateway", zap.String("reason", err.Error())) 19 | return err 20 | } 21 | 22 | if err := authPb.RegisterAuthenticationsHandler(ctx, mux, conn); err != nil { 23 | logger.Log.Fatal("failed to start HTTP gateway", zap.String("reason", err.Error())) 24 | return err 25 | } 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /modules/user-profile/v1/models/user_profile.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/deqode/GoArcc/logger" 5 | "github.com/deqode/GoArcc/protos/types" 6 | "go.uber.org/zap" 7 | "gorm.io/gorm" 8 | "time" 9 | ) 10 | 11 | type UserProfile struct { 12 | ID string `gorm:"primarykey"` 13 | Name string 14 | UserName string 15 | Email string 16 | PhoneNumber string 17 | Sub string `gorm:"uniqueIndex"` 18 | ProfilePicURL string 19 | Source types.VCSProviders 20 | CreatedAt time.Time 21 | UpdatedAt time.Time 22 | DeletedAt gorm.DeletedAt `gorm:"index"` 23 | } 24 | 25 | func InitialMigrationUserProfile(db *gorm.DB) { 26 | if err := db.AutoMigrate(&UserProfile{}); err != nil { 27 | logger.Log.Debug("unable to migrate user profile service", zap.Error(err)) 28 | } 29 | //Check if table exist or not 30 | if !db.Migrator().HasTable(&UserProfile{}) { 31 | // if table not exist then create table 32 | if err := db.Migrator().CreateTable(&UserProfile{}); err != nil { 33 | logger.Log.Debug("unable to create UserProfile table") 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Deqode 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 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | grpc: 2 | port: 8000 3 | host: localhost 4 | request_timeout: 20 5 | 6 | graphql: 7 | port: 8081 8 | host: localhost 9 | request_Timeout: 20 10 | 11 | rest: 12 | port: 8082 13 | host: localhost 14 | request_timeout: 20 15 | 16 | health_check: 17 | port: 8083 18 | host: 0.0.0.0 19 | 20 | cadence_config: 21 | domain: simple-domain 22 | port: 7933 23 | host: localhost 24 | service: cadence-frontend 25 | 26 | promthesius: 27 | port: 8084 28 | host: localhost 29 | 30 | logger: 31 | log_level: -1 32 | 33 | postgres: 34 | host: localhost 35 | port: 5432 36 | user: postgres 37 | password: root 38 | db_name: alfred 39 | ssl_mode: true 40 | pg_driver: pgx 41 | 42 | metrics: 43 | url: 0.0.0.0:7070 44 | service_name: AlfredMetrics 45 | 46 | jaeger: 47 | host: localhost 48 | port: 6831 49 | service_name: AlfredTracing 50 | log_spans: false 51 | 52 | auth: 53 | auth0_client_id: BFnfdaibKSdqkSAOksr3XuUNJuCW9zbZ 54 | auth0_domain: alfred-sh.us.auth0.com 55 | auth0_client_secret: e6JJn1cKSEHk8p1FuoBdDbhmWWINevDitFBsWMTkZi8BbL8AVTQlLfgkwSCyyqJX 56 | auth0_call_back_url: http://localhost:3000/authentication/callback 57 | -------------------------------------------------------------------------------- /db/connection.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "github.com/deqode/GoArcc/config" 6 | "github.com/deqode/GoArcc/logger" 7 | "go.uber.org/zap" 8 | "gorm.io/driver/postgres" 9 | "gorm.io/gorm" 10 | ) 11 | 12 | // NewConnection NewConnection: will open the connection with the database information 13 | // that is passed as an argument 14 | func NewConnection(config *config.Config) *gorm.DB { 15 | psqlInfo := fmt.Sprintf("host=%s port=%s user=%s "+ 16 | "password=%s dbname=%s sslmode=disable Timezone=Asia/Shanghai", 17 | config.Postgres.Host, config.Postgres.Port, config.Postgres.User, config.Postgres.Password, config.Postgres.DbName) 18 | 19 | // https://github.com/go-gorm/postgres 20 | db, err := gorm.Open(postgres.New(postgres.Config{ 21 | DSN: psqlInfo, 22 | PreferSimpleProtocol: true, // disables implicit prepared statement usage 23 | }), &gorm.Config{}) 24 | 25 | if err != nil { 26 | logger.Log.Fatal("GORM connection failed", zap.Error(err)) 27 | panic(err) 28 | } 29 | 30 | logger.Log.Info("connection established with the database") 31 | //No need to close the connection because we have a single pool of connection 32 | return db 33 | } 34 | -------------------------------------------------------------------------------- /modules/user-profile/v1/external-svc/get_user_profile_by_sub.go: -------------------------------------------------------------------------------- 1 | package external_svc 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | model "github.com/deqode/GoArcc/modules/user-profile/v1/models" 7 | "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | func (s *userProfilesServer) GetUserProfileBySub(ctx context.Context, in *pb.GetUserProfileBySubRequest) (*pb.UserProfile, error) { 12 | if err := in.Validate(); err != nil { 13 | return nil, err 14 | } 15 | //user profile model 16 | usrProfile := model.UserProfile{} 17 | gormDb := s.db 18 | //ie: Select * from account where id = in.id 19 | if err := gormDb.First(&usrProfile, "sub= ?", in.Sub).Error; err != nil { 20 | if errors.Is(err, gorm.ErrRecordNotFound) { 21 | return nil, err 22 | } 23 | return nil, err 24 | } 25 | return &pb.UserProfile{ 26 | Id: usrProfile.ID, 27 | Sub: usrProfile.Sub, 28 | Name: usrProfile.Name, 29 | UserName: usrProfile.UserName, 30 | Email: usrProfile.Email, 31 | PhoneNumber: usrProfile.PhoneNumber, 32 | ExternalSource: usrProfile.Source, 33 | ProfilePicUrl: usrProfile.ProfilePicURL, 34 | }, nil 35 | } 36 | -------------------------------------------------------------------------------- /protos/google/api/annotation.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Google Inc. 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/api/invoker.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/deqode/GoArcc/servers/cleanup" 5 | "github.com/deqode/GoArcc/servers/graphql" 6 | "github.com/deqode/GoArcc/servers/grpc" 7 | "github.com/deqode/GoArcc/servers/healthcheck" 8 | "github.com/deqode/GoArcc/servers/rest" 9 | "go.uber.org/fx" 10 | ) 11 | 12 | /* 13 | Todo Here you can new invokers. 14 | Todo Alarm !!!!!! Do not touch the invocation sequence, either you might go through sleepless nights 15 | */ 16 | 17 | // GetInvokersOptions GetInvokersOptions: Please do not change the sequence because it invoker is lifecycle based method . 18 | // So changing the sequence will be harmful. 19 | func GetInvokersOptions() fx.Option { 20 | return fx.Invoke( 21 | //run server will run Rest , Graphql , prometheus server 22 | //RunServer, 23 | //all service got registered 24 | grpc.RunGRPCServer, 25 | grpc.RegisterGrpcModules, 26 | rest.RunRestServer, 27 | graphql.RunGraphqlServer, 28 | //After Registering Grpc Modules then only we can use prometheus 29 | //prometheusServer.PrometheusRunner, 30 | //run cleanup code after closing the server 31 | //Add Health check 32 | healthcheck.HealthCheckRunner, 33 | cleanup.Cleanup, 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /modules/user-profile/v1/external-svc/get_user_profile.go: -------------------------------------------------------------------------------- 1 | package external_svc 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | model "github.com/deqode/GoArcc/modules/user-profile/v1/models" 7 | "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 8 | "github.com/deqode/GoArcc/util/userinfo" 9 | "github.com/golang/protobuf/ptypes/empty" 10 | "gorm.io/gorm" 11 | ) 12 | 13 | func (s *userProfilesServer) GetUserProfile(ctx context.Context, empty *empty.Empty) (*pb.UserProfile, error) { 14 | //user profile model 15 | var usrProfile model.UserProfile 16 | usrId := userinfo.FromContext(ctx).ID 17 | gormDb := s.db 18 | //ie: Select * from account where id = in.id 19 | if err := gormDb.First(&usrProfile, "id= ?", usrId).Error; err != nil { 20 | if errors.Is(err, gorm.ErrRecordNotFound) { 21 | return nil, err 22 | } 23 | return nil, err 24 | } 25 | return &pb.UserProfile{ 26 | Id: usrProfile.ID, 27 | Sub: usrProfile.Sub, 28 | Name: usrProfile.Name, 29 | UserName: usrProfile.UserName, 30 | Email: usrProfile.Email, 31 | PhoneNumber: usrProfile.PhoneNumber, 32 | ExternalSource: usrProfile.Source, 33 | ProfilePicUrl: usrProfile.ProfilePicURL, 34 | }, nil 35 | } 36 | -------------------------------------------------------------------------------- /modules/user-profile/v1/pb/user_profile_int.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package goarcc.user_profile_internal.v1; 3 | 4 | option go_package = "/pb"; 5 | 6 | import "google/protobuf/empty.proto"; 7 | import "user_profile.proto"; 8 | import "google/api/annotation.proto"; 9 | import "validate/validate.proto"; 10 | 11 | 12 | //Private Service - Requires Authentication 13 | service UserProfileInternal { 14 | //CreateUserProfile creates a user profile by external oAuth 15 | rpc CreateUserProfile(CreateUserProfileRequest) returns (goarcc.user_profile.v1.UserProfile); 16 | 17 | // CreateUserProfile will update userprofile 18 | rpc UpdateUserProfile(UpdateUserProfileRequest) returns (goarcc.user_profile.v1.UserProfile); 19 | 20 | // DeleteUserProfile delete the user 21 | rpc DeleteUserProfile(DeleteUserProfileRequest) returns (google.protobuf.Empty); 22 | } 23 | 24 | 25 | message CreateUserProfileRequest { 26 | goarcc.user_profile.v1.UserProfile user_profile = 1; 27 | } 28 | 29 | message DeleteUserProfileRequest { 30 | string id = 1 [(validate.rules).string.min_len = 3]; 31 | } 32 | 33 | message UpdateUserProfileRequest { 34 | goarcc.user_profile.v1.UserProfile user_profile = 1 [(validate.rules).message.required = true]; 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Backend GO CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | name: Test Code 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Install Go 14 | uses: actions/setup-go@v2 15 | with: 16 | go-version: 1.16.x 17 | - uses: actions/checkout@v2 18 | 19 | - uses: actions/cache@v2 20 | with: 21 | path: /tmp/docker_cache/go_mod 22 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}- 23 | restore-keys: | 24 | ${{ runner.os }}-go- 25 | # # In this step, this action saves a list of existing images, 26 | # # the cache is created without them in the post run. 27 | # # It also restores the cache if it exists. 28 | # - uses: satackey/action-docker-layer-caching@v0.0.11 29 | # # Ignore the failure of a step and avoid terminating the job. 30 | # continue-on-error: true 31 | 32 | - name: go mod download 33 | run: docker-compose run backend_api go mod download 34 | 35 | - name: Lint 36 | run: docker-compose run backend_api make run-linter 37 | 38 | - name: Test In Docker 39 | run: docker-compose run backend_api make test 40 | 41 | 42 | - name: build 43 | run: docker-compose run backend_api make build 44 | 45 | - run: docker-compose down 46 | -------------------------------------------------------------------------------- /modules/user-profile/v1/external-svc/get_user_profile_test.go: -------------------------------------------------------------------------------- 1 | package external_svc_test 2 | 3 | import ( 4 | "context" 5 | "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 6 | "github.com/deqode/GoArcc/util/userinfo" 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | "gorm.io/gorm" 10 | _ "log" 11 | "time" 12 | ) 13 | 14 | var _ = Describe("GetUserProfile", func() { 15 | var ( 16 | userProfileServer pb.UserProfilesServer 17 | ctx context.Context 18 | ) 19 | 20 | // this block will run after each it block 21 | BeforeEach(func() { 22 | userProfileServer = UserProfileServerTest 23 | ctx = CtxTest 24 | }) 25 | 26 | // negative 27 | By("internal or external call") 28 | Context("Get an error when id is wrong", func() { 29 | It("Return record not found error", func() { 30 | ui := userinfo.UserInfo{ 31 | ID: "Id", 32 | Email: "Email", 33 | Sub: "Sub", 34 | TokenExpiry: time.Time{}, 35 | } 36 | newCtx := userinfo.NewContext(context.Background(), ui) 37 | _, err := userProfileServer.GetUserProfile(newCtx, nil) 38 | Expect(err).Should(Equal(gorm.ErrRecordNotFound)) 39 | }) 40 | }) 41 | // positive 42 | Context("Get a record when id is provided", func() { 43 | It("should return requested field in the object", func() { 44 | _, err := userProfileServer.GetUserProfile(ctx, nil) 45 | Expect(err).Should(BeNil()) 46 | }) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /client/grpcClient/client.go: -------------------------------------------------------------------------------- 1 | package grpcClient 2 | 3 | import ( 4 | "context" 5 | "github.com/deqode/GoArcc/config" 6 | "github.com/deqode/GoArcc/logger" 7 | "github.com/deqode/GoArcc/servers/openTracing/tracer/jaeger" 8 | grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" 9 | "go.uber.org/zap" 10 | "google.golang.org/grpc" 11 | "time" 12 | ) 13 | 14 | //ClientContext to store context 15 | type ClientContext struct { 16 | Ctx context.Context 17 | } 18 | 19 | //GetGrpcClientConnection is used to get the client connection 20 | func GetGrpcClientConnection(config *config.Config) *grpc.ClientConn { 21 | var opts []grpc.DialOption 22 | opts = append(opts, 23 | grpc.WithUnaryInterceptor( 24 | //tracing for unary interceptors 25 | grpc_opentracing.UnaryClientInterceptor(grpc_opentracing.WithTracer(jaeger.Tracer)), 26 | ), 27 | //tracing for stream interceptors 28 | grpc.WithStreamInterceptor(grpc_opentracing.StreamClientInterceptor(grpc_opentracing.WithTracer(jaeger.Tracer))), 29 | ) 30 | 31 | //append grpc insecure 32 | opts = append(opts, 33 | grpc.WithInsecure(), 34 | ) 35 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(config.Grpc.RequestTimeout)) 36 | defer cancel() 37 | 38 | // You must have some sort of OpenTracing Tracer instance on hand. 39 | conn, err := grpc.DialContext(ctx, config.Grpc.Host+":"+config.Grpc.Port, opts...) 40 | if err != nil { 41 | logger.Log.Fatal("did not connect", zap.Error(err)) 42 | } 43 | return conn 44 | } 45 | -------------------------------------------------------------------------------- /modules/user-profile/v1/internal-svc/create_user_profile.go: -------------------------------------------------------------------------------- 1 | package internal_svc 2 | 3 | import ( 4 | "context" 5 | model "github.com/deqode/GoArcc/modules/user-profile/v1/models" 6 | "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 7 | "google.golang.org/grpc/codes" 8 | "google.golang.org/grpc/status" 9 | "gorm.io/gorm" 10 | "time" 11 | ) 12 | 13 | func (s *userProfileInServer) CreateUserProfile(ctx context.Context, in *pb.CreateUserProfileRequest) (*pb.UserProfile, error) { 14 | if in.UserProfile == nil || in == nil { 15 | return nil, status.Error(codes.FailedPrecondition, "UserProfile not provided") 16 | } 17 | //request validation 18 | if err := in.Validate(); err != nil { 19 | return nil, err 20 | } 21 | 22 | //prepare insert object 23 | UserProfileModel := &model.UserProfile{ 24 | ID: in.GetUserProfile().GetId(), 25 | Name: in.GetUserProfile().GetName(), 26 | UserName: in.GetUserProfile().GetUserName(), 27 | Email: in.GetUserProfile().GetEmail(), 28 | PhoneNumber: in.GetUserProfile().GetPhoneNumber(), 29 | Sub: in.GetUserProfile().GetId(), 30 | ProfilePicURL: in.GetUserProfile().GetProfilePicUrl(), 31 | Source: in.GetUserProfile().GetExternalSource(), 32 | CreatedAt: time.Time{}, 33 | UpdatedAt: time.Time{}, 34 | DeletedAt: gorm.DeletedAt{}, 35 | } 36 | //insert into db 37 | gormDb := s.db 38 | tx := gormDb.Create(UserProfileModel) 39 | if tx.Error != nil { 40 | return nil, tx.Error 41 | } 42 | return in.UserProfile, nil 43 | } 44 | -------------------------------------------------------------------------------- /servers/cleanup/cleanup.go: -------------------------------------------------------------------------------- 1 | package cleanup 2 | 3 | import ( 4 | "context" 5 | "github.com/deqode/GoArcc/logger" 6 | "go.uber.org/fx" 7 | "google.golang.org/grpc" 8 | "io" 9 | ) 10 | 11 | // Config CleanupConfig : cleanup config is the configuration of closing the instance. 12 | // more type will be added in cleanup config if we stop or destroy the instance. 13 | type Config struct { 14 | //Db *gorm.DB 15 | GrpcServerConnection *grpc.Server 16 | GrpcClientConnection *grpc.ClientConn 17 | JaegerCloser io.Closer 18 | } 19 | 20 | // GetCleanupConfig GetCleanupConfig: Get cleanup config is the constructor. 21 | // required all the closing instance 22 | func GetCleanupConfig( 23 | GrpcServerConnection *grpc.Server, 24 | GrpcClientConnection *grpc.ClientConn, 25 | JaegerCloser io.Closer) *Config { 26 | return &Config{ 27 | GrpcServerConnection: GrpcServerConnection, 28 | JaegerCloser: JaegerCloser, 29 | GrpcClientConnection: GrpcClientConnection, 30 | } 31 | } 32 | 33 | // Cleanup /* 34 | func Cleanup(lc fx.Lifecycle, config *Config) { 35 | lc.Append(fx.Hook{ 36 | OnStop: func(ctx context.Context) error { 37 | logger.Log.Info(".......Starting Cleanup code ......") 38 | 39 | //Closing client connection 40 | config.GrpcClientConnection.Close() 41 | logger.Log.Info("successfully closed grpc client connection") 42 | //Closing grpc server connection 43 | config.GrpcServerConnection.GracefulStop() 44 | logger.Log.Info("successfully closed grpc server connection") 45 | //todo more cleanup code will be added 46 | defer config.JaegerCloser.Close() 47 | logger.Log.Info("cleanup code successfully executed") 48 | return nil 49 | }, 50 | }) 51 | 52 | } 53 | -------------------------------------------------------------------------------- /modules/authentication/v1/external-svc/login_callback_test.go: -------------------------------------------------------------------------------- 1 | package external_svc_test 2 | 3 | import ( 4 | "context" 5 | "github.com/deqode/GoArcc/modules/authentication/v1/pb" 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/status" 10 | ) 11 | 12 | var _ = Describe("Login Callback", func() { 13 | var ( 14 | authServer pb.AuthenticationsServer 15 | //authenticator *external_svc.Authenticator 16 | ) 17 | BeforeEach(func() { 18 | authServer = AuthServerTest 19 | //authenticator = AuthenticatorTest 20 | }) 21 | 22 | Describe("Login Callback Test", func() { 23 | By("By Rpc Calls") 24 | Context("Get an error when request object is nil", func() { 25 | It("Failed precondition err must be responded", func() { 26 | _, err := authServer.LoginCallback(context.Background(), nil) 27 | Expect(err).Should(Equal(status.Error(codes.FailedPrecondition, "Request is Nil"))) 28 | }) 29 | }) 30 | Context("Get an error if state or code is empty", func() { 31 | It("Failed precondition err must be responded", func() { 32 | _, err := authServer.LoginCallback(context.Background(), &pb.LoginCallbackRequest{Code: "", State: ""}) 33 | Expect(err).ShouldNot(BeNil()) 34 | }) 35 | }) 36 | 37 | Context("Get an error if user profile is not present", func() { 38 | It("gorm error must be thrown", func() { 39 | 40 | }) 41 | }) 42 | 43 | Context("Get CallBack Response when request object is valid", func() { 44 | It("Throw error if response object is nil", func() { 45 | }) 46 | It("Throw error if user id is empty", func() { 47 | }) 48 | It("Throw error if Access token is empty", func() { 49 | }) 50 | It("Throw error if raw id is empty", func() { 51 | }) 52 | }) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /modules/user-profile/v1/external-svc/get_user_profile_by_sub_test.go: -------------------------------------------------------------------------------- 1 | package external_svc_test 2 | 3 | import ( 4 | "context" 5 | pb "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | var _ = Describe("GetUserProfile By unique External Sub", func() { 12 | var ( 13 | userProfileServer pb.UserProfilesServer 14 | ctx context.Context 15 | usrProfile *pb.UserProfile 16 | ) 17 | 18 | // this block will run after each it block 19 | BeforeEach(func() { 20 | userProfileServer = UserProfileServerTest 21 | ctx = CtxTest 22 | usrProfile = UsrProfile 23 | }) 24 | 25 | Describe("Get a user-profile", func() { 26 | By("internal or external call") 27 | Context("Get an error when sub is empty", func() { 28 | It("it should return validation error", func() { 29 | _, err := userProfileServer.GetUserProfileBySub(ctx, &pb.GetUserProfileBySubRequest{Sub: ""}) 30 | Expect(err.(pb.GetUserProfileBySubRequestValidationError).Reason()).Should(Equal("value length must be at least 3 runes")) 31 | }) 32 | }) 33 | Context("Get an error when sub_id is wrong", func() { 34 | It("should return not found error", func() { 35 | _, err := userProfileServer.GetUserProfileBySub(ctx, &pb.GetUserProfileBySubRequest{Sub: "fji5895"}) 36 | Expect(err).Should(Equal(gorm.ErrRecordNotFound)) 37 | }) 38 | }) 39 | 40 | Context("Get a record when sub_id is provided", func() { 41 | It("should return requested field in the object", func() { 42 | usr, err := userProfileServer.GetUserProfileBySub(ctx, &pb.GetUserProfileBySubRequest{Sub: usrProfile.Sub}) 43 | Expect(err).Should(BeNil()) 44 | Expect(usr.Sub).Should(Equal(usrProfile.Sub)) 45 | }) 46 | }) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /modules/authentication/v1/external-svc/external_suite_test.go: -------------------------------------------------------------------------------- 1 | package external_svc_test 2 | 3 | import ( 4 | "github.com/deqode/GoArcc/client/grpcClient" 5 | "github.com/deqode/GoArcc/config" 6 | "github.com/deqode/GoArcc/db" 7 | "github.com/deqode/GoArcc/logger" 8 | "github.com/deqode/GoArcc/modules/authentication/v1/external-svc" 9 | "github.com/deqode/GoArcc/modules/authentication/v1/pb" 10 | "go.uber.org/zap" 11 | "google.golang.org/grpc" 12 | "gorm.io/gorm" 13 | "log" 14 | "testing" 15 | 16 | . "github.com/onsi/ginkgo" 17 | . "github.com/onsi/gomega" 18 | ) 19 | 20 | var ( 21 | AuthServerTest pb.AuthenticationsServer 22 | ) 23 | 24 | func TestAuthenticationExt(t *testing.T) { 25 | //now init logger 26 | logger.Init(logger.Config{ 27 | LogLevel: zap.DebugLevel, 28 | Development: false, 29 | }) 30 | RegisterFailHandler(Fail) 31 | RunSpecs(t, "TestAuthenticationExt Service Suite") 32 | } 33 | 34 | // Before Suite Run only once 35 | var _ = BeforeSuite(func() { 36 | //getting config 37 | cfgFile, err := config.LoadConfig("config", config.GetConfigDirectory()) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | cfg, err := config.ParseConfig(cfgFile) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | fields := struct { 46 | db *gorm.DB 47 | config *config.Config 48 | grpcClient *grpc.ClientConn 49 | }{ 50 | db: db.NewConnection(cfg), 51 | config: cfg, 52 | grpcClient: grpcClient.GetGrpcClientConnection(cfg), 53 | } 54 | 55 | authServer := external_svc.NewAuthenticationServer(fields.db, fields.config, fields.grpcClient) 56 | _, err = external_svc.NewAuthenticator(fields.config) 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | 61 | AuthServerTest = authServer 62 | }) 63 | 64 | // nil all global variables 65 | var _ = AfterSuite(func() { 66 | AuthServerTest = nil 67 | }) 68 | -------------------------------------------------------------------------------- /servers/graphql/middleware/logger.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | "net/http" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | // AddLogger logs request/response pair 11 | func AddLogger(logger *zap.Logger, h http.Handler) http.Handler { 12 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 13 | ctx := r.Context() 14 | // We do not want to be spammed by Kubernetes health check. 15 | // Do not log Kubernetes health check. 16 | // You can change this behavior as you wish. 17 | if r.Header.Get("X-Liveness-Probe") == "Healthz" { 18 | h.ServeHTTP(w, r) 19 | return 20 | } 21 | 22 | id := GetReqID(ctx) 23 | 24 | // Prepare fields to log 25 | var scheme string 26 | if r.TLS != nil { 27 | scheme = "https" 28 | } else { 29 | scheme = "http" 30 | } 31 | proto := r.Proto 32 | method := r.Method 33 | remoteAddr := r.RemoteAddr 34 | userAgent := r.UserAgent() 35 | uri := strings.Join([]string{scheme, "://", r.Host, r.RequestURI}, "") 36 | 37 | // Log HTTP request 38 | logger.Debug("request started", 39 | zap.String("request-id", id), 40 | zap.String("http-scheme", scheme), 41 | zap.String("http-proto", proto), 42 | zap.String("http-method", method), 43 | zap.String("remote-addr", remoteAddr), 44 | zap.String("user-agent", userAgent), 45 | zap.String("uri", uri), 46 | ) 47 | 48 | t1 := time.Now() 49 | 50 | h.ServeHTTP(w, r) 51 | 52 | // Log HTTP response 53 | logger.Debug("request completed", 54 | zap.String("request-id", id), 55 | zap.String("http-scheme", scheme), 56 | zap.String("http-proto", proto), 57 | zap.String("http-method", method), 58 | zap.String("remote-addr", remoteAddr), 59 | zap.String("user-agent", userAgent), 60 | zap.String("uri", uri), 61 | zap.Float64("elapsed-ms", float64(time.Since(t1).Nanoseconds())/1000000.0), 62 | ) 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /servers/openTracing/tracer/jaeger/jaeger.go: -------------------------------------------------------------------------------- 1 | package jaeger 2 | 3 | import ( 4 | "github.com/deqode/GoArcc/config" 5 | "github.com/deqode/GoArcc/logger" 6 | "github.com/opentracing/opentracing-go" 7 | "github.com/uber/jaeger-client-go" 8 | jaegerconfig "github.com/uber/jaeger-client-go/config" 9 | jaegerlog "github.com/uber/jaeger-client-go/log" 10 | "github.com/uber/jaeger-lib/metrics" 11 | "go.uber.org/zap" 12 | "io" 13 | "strconv" 14 | ) 15 | 16 | // Jaeger struct for conversion 17 | type Jaeger struct { 18 | Host string 19 | ServiceName string 20 | LogSpans bool 21 | } 22 | 23 | var ( 24 | Tracer opentracing.Tracer 25 | ) 26 | 27 | // InitJaeger todo close the connection 28 | // InitJaeger is responsible for initialising the jaeger tracing instance. 29 | func InitJaeger(config *config.Config) (io.Closer, opentracing.Tracer) { 30 | //conversion string val to bool 31 | logSpan, _ := strconv.ParseBool(config.Jaeger.LogSpans) 32 | //Jaeger configuration setup 33 | jaegerCfgInstance := jaegerconfig.Configuration{ 34 | //Service name is the name of project for which tracing will be present 35 | ServiceName: config.Jaeger.ServiceName, 36 | Sampler: &jaegerconfig.SamplerConfig{ 37 | // SamplerTypeConst is the type of sampler that always makes the same decision. 38 | Type: jaeger.SamplerTypeConst, 39 | Param: 1, 40 | }, 41 | //Reporter configuration 42 | Reporter: &jaegerconfig.ReporterConfig{ 43 | LogSpans: logSpan, 44 | //Local Agent Host port 45 | LocalAgentHostPort: config.Jaeger.Host + ":" + config.Jaeger.Port, 46 | }, 47 | } 48 | //New Tracer instance 49 | tracer, closer, err := jaegerCfgInstance.NewTracer( 50 | jaegerconfig.Logger(jaegerlog.StdLogger), 51 | jaegerconfig.Metrics(metrics.NullFactory), 52 | ) 53 | if err != nil { 54 | logger.Log.Fatal("cannot create tracer", zap.Error(err)) 55 | panic(err) 56 | } 57 | 58 | logger.Log.Info("Jaeger tracer initiated") 59 | //global tracer setup , opentracing 60 | opentracing.SetGlobalTracer(tracer) 61 | return closer, tracer 62 | } 63 | -------------------------------------------------------------------------------- /servers/grpc/server.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "context" 5 | "github.com/deqode/GoArcc/config" 6 | "github.com/deqode/GoArcc/logger" 7 | "github.com/deqode/GoArcc/servers/grpc/middleware" 8 | grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" 9 | "github.com/opentracing/opentracing-go" 10 | "github.com/prometheus/client_golang/prometheus/promhttp" 11 | "go.uber.org/fx" 12 | "go.uber.org/zap" 13 | "google.golang.org/grpc" 14 | "google.golang.org/grpc/reflection" 15 | "net" 16 | "net/http" 17 | ) 18 | 19 | // InitGrpcBeforeServing InitGrpcBeforeServing: give the instance of server and listener. 20 | func InitGrpcBeforeServing(config *config.Config, tracer opentracing.Tracer) (*grpc.Server, net.Listener) { 21 | listen, err := net.Listen("tcp", config.Grpc.Host+":"+config.Grpc.Port) 22 | if err != nil { 23 | logger.Log.Fatal("not able to initialize grpc server", zap.Error(err)) 24 | panic(err) 25 | } 26 | // gRPC server setup options 27 | var opts []grpc.ServerOption 28 | // add logging middleware 29 | opts = middleware.AddInterceptors(logger.Log, tracer, opts) 30 | // register service 31 | server := grpc.NewServer(opts...) 32 | return server, listen 33 | } 34 | 35 | // RunGRPCServer runs gRPC service on the given port. 36 | func RunGRPCServer(lc fx.Lifecycle, server *grpc.Server, listener net.Listener) error { 37 | lc.Append( 38 | fx.Hook{ 39 | OnStart: func(ctx context.Context) error { 40 | reflection.Register(server) 41 | grpc_prometheus.EnableHandlingTimeHistogram() 42 | grpc_prometheus.Register(server) 43 | http.Handle("/metrics", promhttp.Handler()) 44 | 45 | logger.Log.Info("Prom metrics endpoint registered on /metrics") 46 | 47 | logger.Log.Info("Starting HTTP2/gRPC server...") 48 | // start gRPC server 49 | go server.Serve(listener) 50 | return nil 51 | }, 52 | OnStop: func(ctx context.Context) error { 53 | // start gRPC server 54 | logger.Log.Info("Stopping gRPC server...") 55 | server.GracefulStop() 56 | return nil 57 | }, 58 | }, 59 | ) 60 | 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /modules/user-profile/v1/internal-svc/delete_user_profile_test.go: -------------------------------------------------------------------------------- 1 | package internal_svc_test 2 | 3 | import ( 4 | "context" 5 | "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/status" 10 | ) 11 | 12 | var _ = Describe("DeleteUserProfile", func() { 13 | var ( 14 | userProfileServer pb.UserProfileInternalServer 15 | ctx context.Context 16 | profile *pb.UserProfile 17 | ) 18 | BeforeEach(func() { 19 | profile = UsrProfile 20 | userProfileServer = UserProfileServerIntTest 21 | ctx = CtxTest 22 | }) 23 | 24 | Describe("Deleting a user profile", func() { 25 | //Negative Cases 26 | By("By an internal RPC Call") 27 | Context("Get an error when no user_id provided", func() { 28 | It("Should return validation error", func() { 29 | _, err := userProfileServer.DeleteUserProfile(ctx, &pb.DeleteUserProfileRequest{Id: ""}) 30 | Expect(err.(pb.DeleteUserProfileRequestValidationError).Reason()).Should(Equal("value length must be at least 3 runes")) 31 | }) 32 | }) 33 | 34 | Context("Get an error when request is nil", func() { 35 | It("should return error ", func() { 36 | _, err := userProfileServer.DeleteUserProfile(ctx, nil) 37 | Expect(err).Should(Equal(status.Error(codes.FailedPrecondition, "Request can't be nil"))) 38 | }) 39 | }) 40 | 41 | Context("Return nil when wrong user_id provided", func() { 42 | It("should not perform any action in DB and return nil", func() { 43 | _, err := userProfileServer.DeleteUserProfile(ctx, &pb.DeleteUserProfileRequest{Id: "fejfoeiw0r8290r420"}) 44 | Expect(err).To(BeNil(), "Error") 45 | }) 46 | }) 47 | 48 | //Positive Test Cases 49 | Context("Return confirmation when record soft deleted in DB", func() { 50 | It("should return a boolean value", func() { 51 | _, err := userProfileServer.DeleteUserProfile(ctx, &pb.DeleteUserProfileRequest{Id: profile.Id}) 52 | Expect(err).To(BeNil(), "Error") 53 | }) 54 | }) 55 | }) 56 | 57 | }) 58 | -------------------------------------------------------------------------------- /servers/graphql/server.go: -------------------------------------------------------------------------------- 1 | package graphql 2 | 3 | import ( 4 | "context" 5 | "github.com/deqode/GoArcc/config" 6 | "github.com/deqode/GoArcc/logger" 7 | "github.com/deqode/GoArcc/servers/graphql/middleware" 8 | "net/http" 9 | "time" 10 | 11 | "github.com/ysugimoto/grpc-graphql-gateway/runtime" 12 | "go.uber.org/fx" 13 | "go.uber.org/zap" 14 | "google.golang.org/grpc" 15 | ) 16 | 17 | // RunGraphqlServer : Will start the graphql server. 18 | func RunGraphqlServer(lc fx.Lifecycle, config *config.Config, conn *grpc.ClientConn) { 19 | Ctx, cancel := context.WithCancel(context.Background()) 20 | defer cancel() 21 | 22 | mux := runtime.NewServeMux() 23 | if err := RegisterGraphqlModules(mux, conn); err != nil { 24 | logger.Log.Fatal("not able to register graphql modules", zap.Error(err)) 25 | } 26 | http.Handle("/graphql", mux) 27 | srv := &http.Server{ 28 | Addr: config.Graphql.Host + ":" + config.Graphql.Port, 29 | WriteTimeout: time.Second * time.Duration(config.Graphql.RequestTimeout), 30 | // add handler with middleware 31 | Handler: http.TimeoutHandler(middleware.ChangeContext(middleware.AddCors(middleware.AddRequestID( 32 | middleware.AddLogger(logger.Log, mux)))), time.Second*time.Duration(config.Graphql.RequestTimeout), "Context deadline exceeded"), 33 | } 34 | 35 | lc.Append(fx.Hook{ 36 | // To mitigate the impact of deadlocks in application startup and 37 | // shutdown, Fx imposes a time limit on OnStart and OnStop hooks. By 38 | // default, hooks have a total of 15 seconds to complete. Timeouts are 39 | // passed via Go's usual context.Context. 40 | OnStart: func(ctx context.Context) error { 41 | 42 | // In production, we'd want to separate the Listen and Serve phases for 43 | // better error-handling. 44 | // run HTTP gateway 45 | logger.Log.Info("starting HTTP/GRAPHQL gateway...") 46 | go func() { 47 | _ = srv.ListenAndServe() 48 | }() 49 | 50 | return nil 51 | }, 52 | OnStop: func(ctx context.Context) error { 53 | logger.Log.Info("graceful shutting down Graphql Server") 54 | _ = srv.Shutdown(Ctx) 55 | return nil 56 | }, 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /servers/rest/server.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "context" 5 | "github.com/deqode/GoArcc/config" 6 | "github.com/deqode/GoArcc/logger" 7 | "github.com/deqode/GoArcc/servers/rest/middleware" 8 | "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 9 | "go.uber.org/fx" 10 | "google.golang.org/grpc" 11 | "net/http" 12 | "time" 13 | ) 14 | 15 | // RunRestServer : Will responsible to run the Rest server in different port. 16 | func RunRestServer(lc fx.Lifecycle, config *config.Config, conn *grpc.ClientConn) { 17 | Ctx, cancel := context.WithCancel(context.Background()) 18 | defer cancel() 19 | 20 | mux := runtime.NewServeMux() 21 | if err := RegisterRESTModules(Ctx, mux, conn); err != nil { 22 | panic(err) 23 | } 24 | srv := &http.Server{ 25 | Addr: config.Rest.Host + ":" + config.Rest.Port, 26 | // add handler with middleware 27 | Handler: http.TimeoutHandler( 28 | middleware.AddCors(middleware.AddRequestID(middleware.AddLogger(logger.Log, mux))), 29 | time.Second*time.Duration(config.Rest.RequestTimeout), 30 | "Context deadline exceeded", 31 | ), 32 | //Read Timeout is the time required to read the request body. 33 | WriteTimeout: time.Second * time.Duration(config.Rest.RequestTimeout), 34 | } 35 | 36 | logger.Log.Info("starting HTTP/REST gateway...") 37 | 38 | lc.Append(fx.Hook{ 39 | // To mitigate the impact of deadlocks in application startup and 40 | // shutdown, Fx imposes a time limit on OnStart and OnStop hooks. By 41 | // default, hooks have a total of 15 seconds to complete. Timeouts are 42 | // passed via Go's usual context.Context. 43 | OnStart: func(ctx context.Context) error { 44 | 45 | // In production, we'd want to separate the Listen and Serve phases for 46 | // better error-handling. 47 | // run HTTP gateway 48 | logger.Log.Info("starting HTTP/REST gateway...") 49 | go func() { 50 | _ = srv.ListenAndServe() 51 | }() 52 | 53 | return nil 54 | }, 55 | 56 | OnStop: func(ctx context.Context) error { 57 | logger.Log.Info("Gracefully Shutting down REST server") 58 | _ = srv.Shutdown(Ctx) 59 | return nil 60 | }, 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /servers/grpc/grpcErrors/grpcErrors.go: -------------------------------------------------------------------------------- 1 | package grpcErrors 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/go-redis/redis/v8" 10 | "github.com/pkg/errors" 11 | "google.golang.org/grpc/codes" 12 | ) 13 | 14 | var ( 15 | ErrNotFound = errors.New("Not found") 16 | ErrNoCtxMetaData = errors.New("No ctxhelper metadata") 17 | ErrInvalidSessionID = errors.New("Invalid session id") 18 | ErrEmailExists = errors.New("Email already exists") 19 | ) 20 | 21 | // ParseGRPCErrStatusCode Parse error and get code 22 | func ParseGRPCErrStatusCode(err error) codes.Code { 23 | switch { 24 | case errors.Is(err, sql.ErrNoRows): 25 | return codes.NotFound 26 | case errors.Is(err, redis.Nil): 27 | return codes.NotFound 28 | case errors.Is(err, context.Canceled): 29 | return codes.Canceled 30 | case errors.Is(err, context.DeadlineExceeded): 31 | return codes.DeadlineExceeded 32 | case errors.Is(err, ErrEmailExists): 33 | return codes.AlreadyExists 34 | case errors.Is(err, ErrNoCtxMetaData): 35 | return codes.Unauthenticated 36 | case errors.Is(err, ErrInvalidSessionID): 37 | return codes.PermissionDenied 38 | case strings.Contains(err.Error(), "Validate"): 39 | return codes.InvalidArgument 40 | case strings.Contains(err.Error(), "redis"): 41 | return codes.NotFound 42 | } 43 | return codes.Internal 44 | } 45 | 46 | // MapGRPCErrCodeToHTTPStatus MapGRPCErrCodeToHttpStatus Map GRPC errors codes to http status 47 | func MapGRPCErrCodeToHTTPStatus(code codes.Code) int { 48 | switch code { 49 | case codes.Unauthenticated: 50 | return http.StatusUnauthorized 51 | case codes.AlreadyExists: 52 | return http.StatusBadRequest 53 | case codes.NotFound: 54 | return http.StatusNotFound 55 | case codes.Internal: 56 | return http.StatusInternalServerError 57 | case codes.PermissionDenied: 58 | return http.StatusForbidden 59 | case codes.Canceled: 60 | return http.StatusRequestTimeout 61 | case codes.DeadlineExceeded: 62 | return http.StatusGatewayTimeout 63 | case codes.InvalidArgument: 64 | return http.StatusBadRequest 65 | } 66 | return http.StatusInternalServerError 67 | } 68 | -------------------------------------------------------------------------------- /util/ctxhelper/ctxhelper.go: -------------------------------------------------------------------------------- 1 | package ctxhelper 2 | 3 | import ( 4 | "context" 5 | "go.uber.org/zap" 6 | "time" 7 | ) 8 | 9 | type ctxKey int 10 | 11 | const ( 12 | ctxKeyComponent ctxKey = iota 13 | ctxKeyLogger 14 | ctxKeyReqID 15 | ctxKeyStartTime 16 | ) 17 | 18 | // NewContextComponentName creates a new context that carries the provided 19 | // componentName value. 20 | func NewContextComponentName(ctx context.Context, componentName string) context.Context { 21 | return context.WithValue(ctx, ctxKeyComponent, componentName) 22 | } 23 | 24 | // ComponentNameFromContext extracts a component name from a context. 25 | func ComponentNameFromContext(ctx context.Context) (componentName string, ok bool) { 26 | componentName, ok = ctx.Value(ctxKeyComponent).(string) 27 | return 28 | } 29 | 30 | // NewContextLogger creates a new context that carries the provided logger 31 | // value. 32 | func NewContextLogger(ctx context.Context, logger *zap.Logger) context.Context { 33 | return context.WithValue(ctx, ctxKeyLogger, logger) 34 | } 35 | 36 | // LoggerFromContext extracts a logger from a context. 37 | func LoggerFromContext(ctx context.Context) (logger *zap.Logger, ok bool) { 38 | logger, ok = ctx.Value(ctxKeyLogger).(*zap.Logger) 39 | return 40 | } 41 | 42 | // NewContextRequestID creates a new context that carries the provided request 43 | // ID value. 44 | func NewContextRequestID(ctx context.Context, id string) context.Context { 45 | return context.WithValue(ctx, ctxKeyReqID, id) 46 | } 47 | 48 | // RequestIDFromContext extracts a request ID from a context. 49 | func RequestIDFromContext(ctx context.Context) (id string, ok bool) { 50 | id, ok = ctx.Value(ctxKeyReqID).(string) 51 | return 52 | } 53 | 54 | // NewContextStartTime creates a new context that carries the provided start 55 | // time. 56 | func NewContextStartTime(ctx context.Context, start time.Time) context.Context { 57 | return context.WithValue(ctx, ctxKeyStartTime, start) 58 | } 59 | 60 | // StartTimeFromContext extracts a start time from a context. 61 | func StartTimeFromContext(ctx context.Context) (start time.Time, ok bool) { 62 | start, ok = ctx.Value(ctxKeyStartTime).(time.Time) 63 | return 64 | } 65 | -------------------------------------------------------------------------------- /servers/graphql/middleware/request-id.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "encoding/base64" 7 | "fmt" 8 | "net/http" 9 | "os" 10 | "strings" 11 | "sync/atomic" 12 | ) 13 | 14 | // Code is taken from: https://github.com/go-chi/chi/blob/master/middleware/request_id.go 15 | 16 | // ctxKeyRequestID Key to use when setting the request ID. 17 | type ctxKeyRequestID int 18 | 19 | // RequestIDKey is the key that holds th unique request ID in a request context. 20 | const RequestIDKey ctxKeyRequestID = 0 21 | 22 | var ( 23 | // prefix is const prefix for request ID 24 | prefix string 25 | 26 | // reqID is counter for request ID 27 | reqID uint64 28 | ) 29 | 30 | // init Initializes constant part of request ID 31 | func init() { 32 | hostname, err := os.Hostname() 33 | if hostname == "" || err != nil { 34 | hostname = "localhost" 35 | } 36 | var buf [12]byte 37 | var b64 string 38 | for len(b64) < 10 { 39 | _, _ = rand.Read(buf[:]) 40 | b64 = base64.StdEncoding.EncodeToString(buf[:]) 41 | b64 = strings.NewReplacer("+", "", "/", "").Replace(b64) 42 | } 43 | 44 | prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10]) 45 | } 46 | 47 | // AddRequestID is a middleware that injects a request ID into the context of each 48 | // request. A request ID is a string of the form "host.example.com/random-0001", 49 | // where "random" is a base62 random string that uniquely identifies this go 50 | // process, and where the last number is an atomically incremented request 51 | // counter. 52 | func AddRequestID(h http.Handler) http.Handler { 53 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 54 | myid := atomic.AddUint64(&reqID, 1) 55 | ctx := r.Context() 56 | ctx = context.WithValue(ctx, RequestIDKey, fmt.Sprintf("%s-%06d", prefix, myid)) 57 | h.ServeHTTP(w, r.WithContext(ctx)) 58 | }) 59 | } 60 | 61 | // GetReqID returns a request ID from the given context if one is present. 62 | // Returns the empty string if a request ID cannot be found. 63 | func GetReqID(ctx context.Context) string { 64 | if ctx == nil { 65 | return "" 66 | } 67 | if reqID, ok := ctx.Value(RequestIDKey).(string); ok { 68 | return reqID 69 | } 70 | return "" 71 | } 72 | -------------------------------------------------------------------------------- /servers/rest/middleware/request-id.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "crypto/rand" 6 | "encoding/base64" 7 | "fmt" 8 | "net/http" 9 | "os" 10 | "strings" 11 | "sync/atomic" 12 | ) 13 | 14 | // Code is taken from: https://github.com/go-chi/chi/blob/master/middleware/request_id.go 15 | 16 | // ctxKeyRequestID Key to use when setting the request ID. 17 | type ctxKeyRequestID int 18 | 19 | // RequestIDKey is the key that holds th unique request ID in a request context. 20 | const RequestIDKey ctxKeyRequestID = 0 21 | 22 | var ( 23 | // prefix is const prefix for request ID 24 | prefix string 25 | 26 | // reqID is counter for request ID 27 | reqID uint64 28 | ) 29 | 30 | // init Initializes constant part of request ID 31 | func init() { 32 | hostname, err := os.Hostname() 33 | if hostname == "" || err != nil { 34 | hostname = "localhost" 35 | } 36 | var buf [12]byte 37 | var b64 string 38 | for len(b64) < 10 { 39 | _, _ = rand.Read(buf[:]) 40 | b64 = base64.StdEncoding.EncodeToString(buf[:]) 41 | b64 = strings.NewReplacer("+", "", "/", "").Replace(b64) 42 | } 43 | 44 | prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10]) 45 | } 46 | 47 | // AddRequestID RequestID is a middleware that injects a request ID into the context of each 48 | // request. A request ID is a string of the form "host.example.com/random-0001", 49 | // where "random" is a base62 random string that uniquely identifies this go 50 | // process, and where the last number is an atomically incremented request 51 | // counter. 52 | func AddRequestID(h http.Handler) http.Handler { 53 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 54 | myid := atomic.AddUint64(&reqID, 1) 55 | ctx := r.Context() 56 | ctx = context.WithValue(ctx, RequestIDKey, fmt.Sprintf("%s-%06d", prefix, myid)) 57 | h.ServeHTTP(w, r.WithContext(ctx)) 58 | }) 59 | } 60 | 61 | // GetReqID returns a request ID from the given context if one is present. 62 | // Returns the empty string if a request ID cannot be found. 63 | func GetReqID(ctx context.Context) string { 64 | if ctx == nil { 65 | return "" 66 | } 67 | if reqID, ok := ctx.Value(RequestIDKey).(string); ok { 68 | return reqID 69 | } 70 | return "" 71 | } 72 | -------------------------------------------------------------------------------- /modules/user-profile/v1/pb/user_profile.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package goarcc.user_profile.v1; 3 | 4 | option go_package = "/pb"; 5 | 6 | import "google/api/annotation.proto"; 7 | import "google/protobuf/empty.proto"; 8 | import "include/graphql.proto"; 9 | import "google/protobuf/timestamp.proto"; 10 | import "types/enums.proto"; 11 | import "validate/validate.proto"; 12 | 13 | //Public Service - Requires Authentication 14 | service UserProfiles { 15 | 16 | // GetUserProfile return a profile of a user 17 | rpc GetUserProfile(google.protobuf.Empty) returns (UserProfile){ 18 | option (google.api.http) = { 19 | get: "/v1/user-profile/get-user-profile" 20 | }; 21 | option (graphql.schema) = { 22 | type: QUERY 23 | name: "user" 24 | }; 25 | } 26 | 27 | //GetUserProfileBySub return a user profile by its unique sub provided by vcs 28 | rpc GetUserProfileBySub(GetUserProfileBySubRequest) returns (UserProfile){ 29 | option (google.api.http) = { 30 | get: "/v1/user-profile/get-user-profile-by-sub/{sub}" 31 | }; 32 | option (graphql.schema) = { 33 | type: QUERY 34 | name: "userBySub" 35 | }; 36 | } 37 | } 38 | 39 | 40 | message GetUserProfileBySubRequest{ 41 | string sub = 1 [(validate.rules).string.min_len = 3, (graphql.field) = {required: true}]; 42 | } 43 | 44 | message UserProfile { 45 | string id = 1; 46 | // external unique_id provided by vcs provider 47 | string sub = 2 [(validate.rules).string = {min_len: 3 max_len: 100}, (graphql.field) = {required: true}]; 48 | // name of the user 49 | string name = 3 [(validate.rules).string={min_len: 1 max_len: 100}, (graphql.field) = {required: true}]; 50 | // a username provided by vcs provider 51 | string user_name = 4; 52 | //email of user 53 | string email = 5 [(validate.rules).string.min_len = 0, (validate.rules).string.email= true]; 54 | // phone of user 55 | string phone_number = 6 ; 56 | types.VCSProviders external_source = 7 [(validate.rules).enum = {not_in: [0]}]; 57 | string profile_pic_url = 8 ; 58 | google.protobuf.Timestamp token_valid_till = 9; 59 | } 60 | 61 | -------------------------------------------------------------------------------- /servers/rest/middleware/logger.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "go.uber.org/zap" 7 | "net/http" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | // AddLogger logs request/response pair 13 | func AddLogger(logger *zap.Logger, h http.Handler) http.Handler { 14 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 15 | ctx := r.Context() 16 | 17 | // We do not want to be spammed by Kubernetes health check. 18 | // Do not log Kubernetes health check. 19 | // You can change this behavior as you wish. 20 | if r.Header.Get("X-Liveness-Probe") == "Healthz" { 21 | h.ServeHTTP(w, r) 22 | return 23 | } 24 | 25 | id := GetReqID(ctx) 26 | 27 | // Prepare fields to log 28 | var scheme string 29 | if r.TLS != nil { 30 | scheme = "https" 31 | } else { 32 | scheme = "http" 33 | } 34 | proto := r.Proto 35 | method := r.Method 36 | remoteAddr := r.RemoteAddr 37 | userAgent := r.UserAgent() 38 | uri := strings.Join([]string{scheme, "://", r.Host, r.RequestURI}, "") 39 | 40 | var request interface{} 41 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { 42 | zap.Error(err) 43 | } 44 | 45 | if request == nil { 46 | request = "Request object is nil" 47 | } 48 | // Log HTTP request 49 | logger.Debug("request started", 50 | zap.String("request-id", id), 51 | zap.String("http-scheme", scheme), 52 | zap.String("http-proto", proto), 53 | zap.String("http-method", method), 54 | zap.String("remote-addr", remoteAddr), 55 | zap.String("user-agent", userAgent), 56 | zap.String("uri", uri), 57 | zap.ByteString("request-body", []byte(fmt.Sprintf("%v", request.(interface{})))), 58 | ) 59 | 60 | t1 := time.Now() 61 | h.ServeHTTP(w, r) 62 | 63 | // Log HTTP response 64 | logger.Debug("request completed", 65 | zap.String("request-id", id), 66 | zap.String("http-scheme", scheme), 67 | zap.String("http-proto", proto), 68 | zap.String("http-method", method), 69 | zap.String("remote-addr", remoteAddr), 70 | zap.String("user-agent", userAgent), 71 | zap.String("uri", uri), 72 | zap.Float64("elapsed-ms", float64(time.Since(t1).Nanoseconds())/1000000.0), 73 | ) 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /modules/user-profile/v1/internal-svc/update_user_profile_test.go: -------------------------------------------------------------------------------- 1 | package internal_svc_test 2 | 3 | import ( 4 | "context" 5 | "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | "google.golang.org/grpc/codes" 9 | "google.golang.org/grpc/status" 10 | ) 11 | 12 | var _ = Describe("UpdateUserProfile", func() { 13 | var ( 14 | userProfileServer pb.UserProfileInternalServer 15 | //ctx context.Context 16 | //profile *pb.UserProfile 17 | ) 18 | BeforeEach(func() { 19 | //profile = UsrProfile 20 | userProfileServer = UserProfileServerIntTest 21 | //ctx = CtxTest 22 | }) 23 | 24 | Describe("Update a user profile", func() { 25 | //Negative Test Cases 26 | By("By a internal RPC Call") 27 | Context("Get an error when request object is nil", func() { 28 | It("should return nil exception", func() { 29 | _, err := userProfileServer.UpdateUserProfile(context.Background(), &pb.UpdateUserProfileRequest{UserProfile: nil}) 30 | Expect(err).Should(Equal(status.Error(codes.FailedPrecondition, "UserProfile to update is not provided"))) 31 | }) 32 | }) 33 | 34 | Context("Get an error when user profile object is nil", func() { 35 | It("should return nil exception", func() { 36 | }) 37 | }) 38 | 39 | Context("Get an error when update mask is incorrect ", func() { 40 | It("should return failed precondition error", func() { 41 | }) 42 | }) 43 | 44 | Context("Get an error when update mask contain id ", func() { 45 | It("should return failed precondition error", func() { 46 | }) 47 | }) 48 | Context("Get an error when id is incorrect", func() { 49 | It("should return failed precondition error", func() { 50 | }) 51 | }) 52 | Context("Get an error when user profile does not exist", func() { 53 | It("should return failed precondition error when profile not exist", func() { 54 | }) 55 | }) 56 | 57 | Context("Update user profile when update mask is correct and id is correct", func() { 58 | It("user profile should be updated successfully", func() { 59 | }) 60 | }) 61 | 62 | Context("Update user profile when update mask is correct and id is correct", func() { 63 | It("check the response of object that user profile is updated or not", func() { 64 | }) 65 | }) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /modules/authentication/v1/external-svc/service.go: -------------------------------------------------------------------------------- 1 | package external_svc 2 | 3 | import ( 4 | "context" 5 | oidc "github.com/coreos/go-oidc" 6 | "github.com/deqode/GoArcc/config" 7 | "github.com/deqode/GoArcc/modules/authentication/v1/pb" 8 | "github.com/deqode/GoArcc/modules/user-profile/v1/external-svc" 9 | userProfileInt "github.com/deqode/GoArcc/modules/user-profile/v1/internal-svc" 10 | usrProfilePb "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 11 | "golang.org/x/oauth2" 12 | "google.golang.org/grpc" 13 | "gorm.io/gorm" 14 | "log" 15 | ) 16 | 17 | type authenticationServer struct { 18 | db *gorm.DB 19 | config *config.Config 20 | grpcClient *grpc.ClientConn 21 | userProfileServer usrProfilePb.UserProfilesServer 22 | userProfileInServer usrProfilePb.UserProfileInternalServer 23 | authenticator *Authenticator 24 | } 25 | 26 | // NewAuthenticationServer Service Implementation 27 | func NewAuthenticationServer( 28 | db *gorm.DB, 29 | config *config.Config, 30 | grpcClientConn *grpc.ClientConn, 31 | 32 | ) pb.AuthenticationsServer { 33 | userProfileSrv := external_svc.NewUserProfilesServer(db, config, grpcClientConn) 34 | userProfileInSrv := userProfileInt.NewUserProfileInServer(db, config, grpcClientConn) 35 | authenticatorCli, _ := NewAuthenticator(config) 36 | return &authenticationServer{ 37 | db: db, 38 | config: config, 39 | grpcClient: grpcClientConn, 40 | userProfileServer: userProfileSrv, 41 | userProfileInServer: userProfileInSrv, 42 | authenticator: authenticatorCli, 43 | } 44 | } 45 | 46 | type Authenticator struct { 47 | Provider *oidc.Provider 48 | Config oauth2.Config 49 | Ctx context.Context 50 | } 51 | 52 | func NewAuthenticator(config *config.Config) (*Authenticator, error) { 53 | ctx := context.Background() 54 | 55 | provider, err := oidc.NewProvider(ctx, "https://"+config.Auth.Auth0Domain+"/") 56 | if err != nil { 57 | log.Printf("failed to get provider: %v", err) 58 | return nil, err 59 | } 60 | 61 | conf := oauth2.Config{ 62 | ClientID: config.Auth.Auth0ClientID, 63 | ClientSecret: config.Auth.Auth0ClientSecret, 64 | RedirectURL: config.Auth.Auth0CallBackURL, 65 | Endpoint: provider.Endpoint(), 66 | Scopes: []string{oidc.ScopeOpenID, "profile", "user"}, 67 | } 68 | 69 | return &Authenticator{ 70 | Provider: provider, 71 | Config: conf, 72 | Ctx: ctx, 73 | }, nil 74 | } 75 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/deqode/GoArcc 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/HdrHistogram/hdrhistogram-go v1.1.0 // indirect 7 | github.com/bxcodec/faker/v3 v3.6.0 8 | github.com/coreos/go-oidc v2.2.1+incompatible 9 | github.com/deqode/GoArcc/config v0.0.0-00010101000000-000000000000 10 | github.com/deqode/GoArcc/db v0.0.0-00010101000000-000000000000 11 | github.com/deqode/GoArcc/logger v0.0.0-00010101000000-000000000000 12 | github.com/deqode/GoArcc/protos/types v0.0.0-00010101000000-000000000000 13 | github.com/deqode/GoArcc/servers/grpc v0.0.0-00010101000000-000000000000 14 | github.com/deqode/GoArcc/util/userinfo v0.0.0-00010101000000-000000000000 15 | github.com/dimiro1/health v0.0.0-20191019130555-c5cbb4d46ffc 16 | github.com/envoyproxy/protoc-gen-validate v0.1.0 17 | github.com/golang/protobuf v1.5.2 18 | github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect 19 | github.com/graphql-go/graphql v0.7.9 20 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 21 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.2.0 22 | github.com/kyoh86/richgo v0.3.9 // indirect 23 | github.com/labstack/echo/v4 v4.4.0 24 | github.com/mattn/go-isatty v0.0.13 // indirect 25 | github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect 26 | github.com/onsi/ginkgo v1.15.0 27 | github.com/onsi/gomega v1.10.5 28 | github.com/opentracing/opentracing-go v1.2.0 29 | github.com/pkg/errors v0.9.1 30 | github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect 31 | github.com/prometheus/client_golang v1.11.0 32 | github.com/smartystreets/assertions v1.1.0 // indirect 33 | github.com/uber/jaeger-client-go v2.25.0+incompatible 34 | github.com/uber/jaeger-lib v2.4.0+incompatible 35 | github.com/ysugimoto/grpc-graphql-gateway v0.20.0 36 | go.uber.org/fx v1.13.1 37 | go.uber.org/zap v1.18.1 38 | golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 // indirect 39 | golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 40 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect 41 | golang.org/x/tools v0.1.4 // indirect 42 | google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c 43 | google.golang.org/grpc v1.39.0 44 | google.golang.org/protobuf v1.27.1 45 | gorm.io/gorm v1.21.11 46 | ) 47 | 48 | replace ( 49 | github.com/apache/thrift => github.com/apache/thrift v0.0.0-20190309152529-a9b748bb0e02 50 | github.com/deqode/GoArcc/config => ./config 51 | github.com/deqode/GoArcc/db => ./db 52 | github.com/deqode/GoArcc/logger => ./logger 53 | github.com/deqode/GoArcc/protos/types => ./protos/types 54 | github.com/deqode/GoArcc/servers/grpc => ./servers/grpc 55 | github.com/deqode/GoArcc/util/userinfo => ./util/userinfo 56 | ) 57 | -------------------------------------------------------------------------------- /logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "errors" 5 | "go.uber.org/zap" 6 | "go.uber.org/zap/zapcore" 7 | "os" 8 | "time" 9 | ) 10 | 11 | var ( 12 | // Log is global logger 13 | Log *zap.Logger 14 | 15 | // customTimeFormat is custom Time format 16 | customTimeFormat string 17 | ) 18 | 19 | type Config struct { 20 | LogLevel zapcore.Level 21 | Development bool 22 | } 23 | 24 | // customTimeEncoder encode Time to our custom format 25 | // This example how we can customize zap default functionality 26 | func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { 27 | enc.AppendString(t.Format(customTimeFormat)) 28 | } 29 | 30 | // Init initializes log by input parameters 31 | // lvl - global log level: Debug(-1), Info(0), Warn(1), Error(2), DPanic(3), Panic(4), Fatal(5) 32 | // timeFormat - custom time format for logger of empty string to use default 33 | func Init(config Config) (*zap.Logger, error) { 34 | 35 | if Log != nil { 36 | Log.Fatal("Logger already initialized once, No need to do it multiple times") 37 | return nil, errors.New("logger already initialized once") 38 | } 39 | 40 | // First, define our level-handling logic. 41 | globalLevel := config.LogLevel 42 | logTimeFormat := "" 43 | 44 | // High-priority output should also go to standard error, and low-priority 45 | // output should also go to standard out. 46 | // It is useful for Kubernetes deployment. 47 | // Kubernetes interprets os.Stdout log items as INFO and os.Stderr log items 48 | // as ERROR by default. 49 | highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { 50 | return lvl >= zapcore.ErrorLevel 51 | }) 52 | lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { 53 | return lvl >= globalLevel && lvl < zapcore.ErrorLevel 54 | }) 55 | consoleInfos := zapcore.Lock(os.Stdout) 56 | consoleErrors := zapcore.Lock(os.Stderr) 57 | 58 | // Configure console output. 59 | var useCustomTimeFormat bool 60 | 61 | var ecfg zapcore.EncoderConfig 62 | if config.Development { 63 | ecfg = zap.NewDevelopmentEncoderConfig() 64 | } else { 65 | ecfg = zap.NewProductionEncoderConfig() 66 | } 67 | 68 | if len(logTimeFormat) > 0 { 69 | customTimeFormat = logTimeFormat 70 | ecfg.EncodeTime = customTimeEncoder 71 | useCustomTimeFormat = true 72 | } 73 | consoleEncoder := zapcore.NewJSONEncoder(ecfg) 74 | 75 | // Join the outputs, encoders, and level-handling functions into 76 | // zapcore. 77 | core := zapcore.NewTee( 78 | zapcore.NewCore(consoleEncoder, consoleErrors, highPriority), 79 | zapcore.NewCore(consoleEncoder, consoleInfos, lowPriority), 80 | ) 81 | // From a zapcore.Core, it's easy to construct a Logger. 82 | Log = zap.New(core) 83 | zap.ReplaceGlobals(Log) 84 | zap.RedirectStdLog(Log) 85 | 86 | if !useCustomTimeFormat { 87 | Log.Warn("time format for logger is not provided - use zap default") 88 | } 89 | 90 | return Log, nil 91 | } 92 | -------------------------------------------------------------------------------- /servers/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "github.com/deqode/GoArcc/config" 5 | "github.com/deqode/GoArcc/logger" 6 | "github.com/labstack/echo/v4" 7 | "github.com/prometheus/client_golang/prometheus" 8 | "github.com/prometheus/client_golang/prometheus/promhttp" 9 | "go.uber.org/zap" 10 | "strconv" 11 | ) 12 | 13 | //Metrics details of hits , response time 14 | type Metrics interface { 15 | IncHits(status int, method, path string) 16 | ObserveResponseTime(status int, method, path string, observeTime float64) 17 | } 18 | 19 | type PrometheusMetrics struct { 20 | HitsTotal prometheus.Counter 21 | Hits *prometheus.CounterVec 22 | Times *prometheus.HistogramVec 23 | } 24 | 25 | // CreateMetrics : Create Metrics will create the metrics in prometheus server. 26 | func CreateMetrics(config *config.Config) Metrics { 27 | var metrics PrometheusMetrics 28 | metrics.HitsTotal = prometheus.NewCounter(prometheus.CounterOpts{ 29 | Name: config.Metrics.ServiceName + "_hits_total", 30 | }) 31 | if err := prometheus.Register(metrics.HitsTotal); err != nil { 32 | logger.Log.Fatal("error in registering prometheus hits", zap.Error(err)) 33 | return nil 34 | } 35 | metrics.Hits = prometheus.NewCounterVec( 36 | prometheus.CounterOpts{ 37 | Name: config.Metrics.ServiceName + "_hits", 38 | }, 39 | []string{"status", "method", "path"}, 40 | ) 41 | if err := prometheus.Register(metrics.Hits); err != nil { 42 | logger.Log.Fatal("error in registering prometheus hits", zap.Error(err)) 43 | return nil 44 | } 45 | metrics.Times = prometheus.NewHistogramVec( 46 | prometheus.HistogramOpts{ 47 | Name: config.Metrics.ServiceName + "_times", 48 | }, 49 | []string{"status", "method", "path"}, 50 | ) 51 | if err := prometheus.Register(metrics.Times); err != nil { 52 | logger.Log.Fatal("error in registering prometheus", zap.Error(err)) 53 | return nil 54 | } 55 | if err := prometheus.Register(prometheus.NewBuildInfoCollector()); err != nil { 56 | logger.Log.Fatal("error in registering prometheus", zap.Error(err)) 57 | return nil 58 | } 59 | go func() { 60 | router := echo.New() 61 | router.GET("/metrics", echo.WrapHandler(promhttp.Handler())) 62 | if err := router.Start(config.Metrics.URL); err != nil { 63 | logger.Log.Fatal("unable to create metrics", zap.Error(err)) 64 | } 65 | }() 66 | logger.Log.Info("Metrics available URL: ", zap.String("url", config.Metrics.URL), 67 | zap.String("service name:", config.Metrics.ServiceName)) 68 | return &metrics 69 | } 70 | 71 | //IncHits : increment the total hits 72 | func (metrics *PrometheusMetrics) IncHits(status int, method, path string) { 73 | metrics.HitsTotal.Inc() 74 | metrics.Hits.WithLabelValues(strconv.Itoa(status), method, path).Inc() 75 | } 76 | 77 | //ObserveResponseTime : 78 | func (metrics *PrometheusMetrics) ObserveResponseTime(status int, method, path string, observeTime float64) { 79 | metrics.Times.WithLabelValues(strconv.Itoa(status), method, path).Observe(observeTime) 80 | } 81 | -------------------------------------------------------------------------------- /modules/user-profile/v1/internal-svc/internal_suite_test.go: -------------------------------------------------------------------------------- 1 | package internal_svc_test 2 | 3 | import ( 4 | "context" 5 | "github.com/bxcodec/faker/v3" 6 | "github.com/deqode/GoArcc/client/grpcClient" 7 | "github.com/deqode/GoArcc/config" 8 | "github.com/deqode/GoArcc/db" 9 | "github.com/deqode/GoArcc/logger" 10 | "github.com/deqode/GoArcc/modules/user-profile/v1/internal-svc" 11 | "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 12 | "github.com/deqode/GoArcc/protos/types" 13 | "github.com/deqode/GoArcc/util/userinfo" 14 | . "github.com/onsi/ginkgo" 15 | . "github.com/onsi/gomega" 16 | "go.uber.org/zap" 17 | "google.golang.org/grpc" 18 | "gorm.io/gorm" 19 | "log" 20 | "testing" 21 | "time" 22 | ) 23 | 24 | var ( 25 | UserProfileServerIntTest pb.UserProfileInternalServer 26 | CtxTest context.Context 27 | UsrProfile *pb.UserProfile 28 | ) 29 | 30 | func TestUserProfileInternalSvc(t *testing.T) { 31 | //now init logger 32 | logger.Init(logger.Config{ 33 | LogLevel: zap.DebugLevel, 34 | Development: false, 35 | }) 36 | RegisterFailHandler(Fail) 37 | RunSpecs(t, "TestUserProfile Internal Service Suite") 38 | } 39 | 40 | // This block will run only once 41 | var _ = BeforeSuite(func() { 42 | //getting config 43 | cfgFile, err := config.LoadConfig("config", config.GetConfigDirectory()) 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | cfg, err := config.ParseConfig(cfgFile) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | fields := struct { 52 | db *gorm.DB 53 | config *config.Config 54 | grpcClient *grpc.ClientConn 55 | }{ 56 | db: db.NewConnection(cfg), 57 | config: cfg, 58 | grpcClient: grpcClient.GetGrpcClientConnection(cfg), 59 | } 60 | //Int service initialisation 61 | userProfileIntServer := internal_svc.NewUserProfileInServer(fields.db, fields.config, fields.grpcClient) 62 | 63 | id := "github_" + faker.Username() 64 | // Create a UserProfile before getting 65 | res, err := userProfileIntServer.CreateUserProfile(CtxTest, &pb.CreateUserProfileRequest{UserProfile: &pb.UserProfile{ 66 | Id: id, 67 | Sub: id, 68 | Name: faker.Name(), 69 | UserName: faker.Username(), 70 | Email: faker.Email(), 71 | PhoneNumber: faker.Phonenumber(), 72 | ExternalSource: types.VCSProviders_GITHUB, 73 | ProfilePicUrl: faker.URL(), 74 | TokenValidTill: nil, 75 | }}) 76 | if err != nil { 77 | return 78 | } 79 | ui := userinfo.UserInfo{ 80 | ID: res.Id, 81 | Email: res.Email, 82 | Sub: res.Sub, 83 | TokenExpiry: time.Time{}, 84 | } 85 | 86 | //initialize to global variable here 87 | CtxTest = userinfo.NewContext(context.Background(), ui) 88 | UsrProfile = res 89 | UserProfileServerIntTest = userProfileIntServer 90 | }) 91 | 92 | // must initialize nil to global variable after suit is complete 93 | var _ = AfterSuite(func() { 94 | CtxTest = nil 95 | UsrProfile = nil 96 | UserProfileServerIntTest = nil 97 | }) 98 | -------------------------------------------------------------------------------- /protos/google/api/httpbody.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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/protobuf/any.proto"; 20 | 21 | option cc_enable_arenas = true; 22 | option go_package = "google.golang.org/genproto/googleapis/api/httpbody;httpbody"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "HttpBodyProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | // Message that represents an arbitrary HTTP body. It should only be used for 29 | // payload formats that can't be represented as JSON, such as raw binary or 30 | // an HTML page. 31 | // 32 | // 33 | // This message can be used both in streaming and non-streaming API methods in 34 | // the request as well as the response. 35 | // 36 | // It can be used as a top-level request field, which is convenient if one 37 | // wants to extract parameters from either the URL or HTTP template into the 38 | // request fields and also want access to the raw HTTP body. 39 | // 40 | // Example: 41 | // 42 | // message GetResourceRequest { 43 | // // A unique request id. 44 | // string request_id = 1; 45 | // 46 | // // The raw HTTP body is bound to this field. 47 | // google.api.HttpBody http_body = 2; 48 | // } 49 | // 50 | // service ResourceService { 51 | // rpc GetResource(GetResourceRequest) returns (google.api.HttpBody); 52 | // rpc UpdateResource(google.api.HttpBody) returns 53 | // (google.protobuf.Empty); 54 | // } 55 | // 56 | // Example with streaming methods: 57 | // 58 | // service CaldavService { 59 | // rpc GetCalendar(stream google.api.HttpBody) 60 | // returns (stream google.api.HttpBody); 61 | // rpc UpdateCalendar(stream google.api.HttpBody) 62 | // returns (stream google.api.HttpBody); 63 | // } 64 | // 65 | // Use of this type only changes how the request and response bodies are 66 | // handled, all other features will continue to work unchanged. 67 | message HttpBody { 68 | // The HTTP Content-Type header value specifying the content type of the body. 69 | string content_type = 1; 70 | 71 | // The HTTP request/response body as raw binary. 72 | bytes data = 2; 73 | 74 | // Application specific response metadata. Must be set in the first response 75 | // for streaming APIs. 76 | repeated google.protobuf.Any extensions = 3; 77 | } 78 | -------------------------------------------------------------------------------- /servers/grpc/middleware/interceptors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" 5 | grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth" 6 | grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap" 7 | grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" 8 | grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags" 9 | grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" 10 | grpc_validator "github.com/grpc-ecosystem/go-grpc-middleware/validator" 11 | grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" 12 | "github.com/opentracing/opentracing-go" 13 | "go.uber.org/zap" 14 | "google.golang.org/grpc" 15 | "google.golang.org/grpc/codes" 16 | "google.golang.org/grpc/status" 17 | ) 18 | 19 | // AddInterceptors AddInterceptors: Add interceptors to the grpc server 20 | func AddInterceptors(logger *zap.Logger, tracer opentracing.Tracer, opts []grpc.ServerOption) []grpc.ServerOption { 21 | // Make sure that log statements internal to gRPC library are logged using the zapLogger as well. 22 | grpc_zap.ReplaceGrpcLoggerV2(logger) 23 | //grpc recovery options 24 | recoveryOptions := []grpc_recovery.Option{ 25 | grpc_recovery.WithRecoveryHandler(grpcPanicsRecovery), 26 | } 27 | 28 | // Add unary interceptor 29 | opts = append(opts, grpc_middleware.WithUnaryServerChain( 30 | //turns grpc panics into unknown error 31 | grpc_recovery.UnaryServerInterceptor(recoveryOptions...), 32 | //for context tags 33 | 34 | grpc_ctxtags.UnaryServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)), 35 | 36 | grpc_opentracing.UnaryServerInterceptor(grpc_opentracing.WithTracer(tracer)), 37 | //Adding prothesis monitoring 38 | grpc_prometheus.UnaryServerInterceptor, 39 | //zap logger implementation 40 | grpc_zap.UnaryServerInterceptor(logger), 41 | 42 | grpc_auth.UnaryServerInterceptor(AuthMiddleware), 43 | 44 | //validate the incoming request - inbound in proto file 45 | //If request is not correct the error will be sent to client 46 | grpc_validator.UnaryServerInterceptor(), 47 | )) 48 | 49 | // Add stream interceptor (added as an example here) 50 | opts = append(opts, grpc_middleware.WithStreamServerChain( 51 | grpc_recovery.StreamServerInterceptor(recoveryOptions...), 52 | //context tag implementation 53 | grpc_ctxtags.StreamServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)), 54 | //open tracing implementation 55 | grpc_opentracing.StreamServerInterceptor(), 56 | //prom implementation 57 | grpc_prometheus.StreamServerInterceptor, 58 | grpc_auth.StreamServerInterceptor(AuthMiddleware), 59 | //zap implementation 60 | grpc_zap.StreamServerInterceptor(logger), 61 | 62 | //validate the incoming request - inbound in proto file 63 | //If request is not correct the error will be sent to client 64 | grpc_validator.StreamServerInterceptor(), 65 | )) 66 | 67 | return opts 68 | } 69 | 70 | //grpcPanicsRecovery: is responsible to convert panic to the custom message 71 | func grpcPanicsRecovery(in interface{}) error { 72 | return status.Errorf(codes.Unknown, "Unknown error") 73 | } 74 | -------------------------------------------------------------------------------- /modules/user-profile/v1/external-svc/external_suite_test.go: -------------------------------------------------------------------------------- 1 | package external_svc_test 2 | 3 | import ( 4 | "context" 5 | "github.com/bxcodec/faker/v3" 6 | "github.com/deqode/GoArcc/client/grpcClient" 7 | "github.com/deqode/GoArcc/config" 8 | "github.com/deqode/GoArcc/db" 9 | "github.com/deqode/GoArcc/logger" 10 | external_svc "github.com/deqode/GoArcc/modules/user-profile/v1/external-svc" 11 | "github.com/deqode/GoArcc/modules/user-profile/v1/internal-svc" 12 | "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 13 | "github.com/deqode/GoArcc/protos/types" 14 | "github.com/deqode/GoArcc/util/userinfo" 15 | . "github.com/onsi/ginkgo" 16 | . "github.com/onsi/gomega" 17 | "go.uber.org/zap" 18 | "google.golang.org/grpc" 19 | "gorm.io/gorm" 20 | "log" 21 | "testing" 22 | "time" 23 | ) 24 | 25 | var ( 26 | UserProfileServerTest pb.UserProfilesServer 27 | CtxTest context.Context 28 | UsrProfile *pb.UserProfile 29 | ) 30 | 31 | func TestUserProfileExternalSvc(t *testing.T) { 32 | //now init logger 33 | logger.Init(logger.Config{ 34 | LogLevel: zap.DebugLevel, 35 | Development: false, 36 | }) 37 | RegisterFailHandler(Fail) 38 | RunSpecs(t, "TestUserProfile External Service Suite") 39 | } 40 | 41 | // This block will run only once 42 | var _ = BeforeSuite(func() { 43 | //getting config 44 | cfgFile, err := config.LoadConfig("config", config.GetConfigDirectory()) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | cfg, err := config.ParseConfig(cfgFile) 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | fields := struct { 53 | db *gorm.DB 54 | config *config.Config 55 | grpcClient *grpc.ClientConn 56 | }{ 57 | db: db.NewConnection(cfg), 58 | config: cfg, 59 | grpcClient: grpcClient.GetGrpcClientConnection(cfg), 60 | } 61 | //Ext service initialisation 62 | userProfileServer := external_svc.NewUserProfilesServer(fields.db, fields.config, fields.grpcClient) 63 | //Int service initialisation 64 | userProfileIntServer := internal_svc.NewUserProfileInServer(fields.db, fields.config, fields.grpcClient) 65 | 66 | id := "github_" + faker.Username() 67 | // Create a UserProfile before getting 68 | res, err := userProfileIntServer.CreateUserProfile(CtxTest, &pb.CreateUserProfileRequest{UserProfile: &pb.UserProfile{ 69 | Id: id, 70 | Sub: id, 71 | Name: faker.Name(), 72 | UserName: faker.Username(), 73 | Email: faker.Email(), 74 | PhoneNumber: faker.Phonenumber(), 75 | ExternalSource: types.VCSProviders_GITHUB, 76 | ProfilePicUrl: faker.URL(), 77 | TokenValidTill: nil, 78 | }}) 79 | if err != nil { 80 | return 81 | } 82 | ui := userinfo.UserInfo{ 83 | ID: res.Id, 84 | Email: res.Email, 85 | Sub: res.Sub, 86 | TokenExpiry: time.Time{}, 87 | } 88 | 89 | //initialize to global variable here 90 | CtxTest = userinfo.NewContext(context.Background(), ui) 91 | UsrProfile = res 92 | UserProfileServerTest = userProfileServer 93 | }) 94 | 95 | // must initialize nil to global variable after suit is complete 96 | var _ = AfterSuite(func() { 97 | CtxTest = nil 98 | UsrProfile = nil 99 | UserProfileServerTest = nil 100 | }) 101 | -------------------------------------------------------------------------------- /modules/authentication/v1/pb/authentication.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package goarcc.authentication.v1; 3 | 4 | option go_package = "/pb"; 5 | 6 | import "google/api/annotation.proto"; 7 | import "google/protobuf/empty.proto"; 8 | import "validate/validate.proto"; 9 | 10 | service ValidateLoginService { 11 | rpc ValidateUserLogin (ValidateUserLoginRequest) returns (ValidateUserLoginResponse); 12 | } 13 | 14 | //ValidateUserLoginRequest contains user login credential. 15 | message ValidateUserLoginRequest { 16 | //Id is the user Unique identifier 17 | string id = 1; 18 | // Password is the user password 19 | string password = 2; 20 | } 21 | 22 | message ValidateUserLoginResponse { 23 | //Id is the user Unique identifier 24 | string id = 1; 25 | } 26 | 27 | service Authentications { 28 | // Login provide a url of External OAuth login endpoint (Auth0) 29 | rpc Login (google.protobuf.Empty) returns (LoginResponse) { 30 | option (google.api.http) = { 31 | get: "/v1/authentication/login" 32 | }; 33 | }; 34 | 35 | // LoginCallback receives a callback from external OAuth application 36 | rpc LoginCallback (LoginCallbackRequest) returns (LoginCallbackResponse) { 37 | option (google.api.http) = { 38 | get: "/v1/authentication/callback" 39 | }; 40 | }; 41 | 42 | rpc Logout (google.protobuf.Empty) returns (google.protobuf.Empty) { 43 | option (google.api.http) = { 44 | get: "/v1/authentication/logout" 45 | }; 46 | }; 47 | 48 | // GetUserLogin returns the specified user by its id. 49 | // rpc GetUserLogin (GetUserLoginRequest) returns (google.protobuf.Empty); 50 | 51 | // DeleteUserLogin is used to delete a user from the system, this will delete all 52 | // rpc DeleteUserLogin (DeleteUserLoginRequest) returns (google.protobuf.Empty); 53 | 54 | 55 | // rpc UpdateUserPassword (UpdateUserPasswordRequest) returns (google.protobuf.Empty); 56 | 57 | // ResetUserPassword , if a user has forgot the password then 58 | // this method can be used to reset the password 59 | // rpc ResetUserPassword (ResetUserPasswordRequest) returns (google.protobuf.Empty); 60 | 61 | } 62 | 63 | message LoginResponse{ 64 | string url = 1; 65 | } 66 | 67 | message LoginCallbackRequest{ 68 | string state = 1 [(validate.rules).string.min_len = 3]; 69 | string code = 2 [(validate.rules).string.min_len = 3]; 70 | } 71 | 72 | message LoginCallbackResponse{ 73 | string id_token = 1; 74 | string access_token = 2; 75 | string user_id = 3; 76 | } 77 | //message GetUserLoginRequest { 78 | // // Id is the unique user id 79 | // string id = 1;// [(validate.rules).string.min_len = 3]; 80 | //} 81 | // 82 | //message DeleteUserLoginRequest { 83 | // // Id is the unique user id 84 | // string id = 1;// [(validate.rules).string.min_len = 3]; 85 | //} 86 | // 87 | //message UpdateUserPasswordRequest { 88 | // // Id is the unique user id 89 | // string id = 1 ;//[(validate.rules).string.min_len = 3]; 90 | // // Password to be added against the given user id. 91 | // string old_password = 2;// [(validate.rules).string = { min_len:8 max_len:36}]; 92 | // string new_password = 3;// [(validate.rules).string = { min_len:8 max_len:36}]; 93 | //} 94 | // 95 | //message ResetUserPasswordRequest { 96 | // // Id is the unique user id 97 | // string id = 1;// [(validate.rules).string.min_len = 3]; 98 | // // Password to be added against the given user id. 99 | // string new_password = 3;// [(validate.rules).string = { min_len:8 max_len:36}]; 100 | //} 101 | -------------------------------------------------------------------------------- /protos/google/api/field_behaviour.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 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/protobuf/descriptor.proto"; 20 | 21 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 22 | option java_multiple_files = true; 23 | option java_outer_classname = "FieldBehaviorProto"; 24 | option java_package = "com.google.api"; 25 | option objc_class_prefix = "GAPI"; 26 | 27 | extend google.protobuf.FieldOptions { 28 | // A designation of a specific field behavior (required, output only, etc.) 29 | // in protobuf messages. 30 | // 31 | // Examples: 32 | // 33 | // string name = 1 [(google.api.field_behavior) = REQUIRED]; 34 | // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; 35 | // google.protobuf.Duration ttl = 1 36 | // [(google.api.field_behavior) = INPUT_ONLY]; 37 | // google.protobuf.Timestamp expire_time = 1 38 | // [(google.api.field_behavior) = OUTPUT_ONLY, 39 | // (google.api.field_behavior) = IMMUTABLE]; 40 | repeated google.api.FieldBehavior field_behavior = 1052; 41 | } 42 | 43 | // An indicator of the behavior of a given field (for example, that a field 44 | // is required in requests, or given as output but ignored as input). 45 | // This **does not** change the behavior in protocol buffers itself; it only 46 | // denotes the behavior and may affect how API tooling handles the field. 47 | // 48 | // Note: This enum **may** receive new values in the future. 49 | enum FieldBehavior { 50 | // Conventional default for enums. Do not use this. 51 | FIELD_BEHAVIOR_UNSPECIFIED = 0; 52 | 53 | // Specifically denotes a field as optional. 54 | // While all fields in protocol buffers are optional, this may be specified 55 | // for emphasis if appropriate. 56 | OPTIONAL = 1; 57 | 58 | // Denotes a field as required. 59 | // This indicates that the field **must** be provided as part of the request, 60 | // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). 61 | REQUIRED = 2; 62 | 63 | // Denotes a field as output only. 64 | // This indicates that the field is provided in responses, but including the 65 | // field in a request does nothing (the server *must* ignore it and 66 | // *must not* throw an error as a result of the field's presence). 67 | OUTPUT_ONLY = 3; 68 | 69 | // Denotes a field as input only. 70 | // This indicates that the field is provided in requests, and the 71 | // corresponding field is not included in output. 72 | INPUT_ONLY = 4; 73 | 74 | // Denotes a field as immutable. 75 | // This indicates that the field may be set once in a request to create a 76 | // resource, but may not be changed thereafter. 77 | IMMUTABLE = 5; 78 | 79 | // Denotes that a (repeated) field is an unordered list. 80 | // This indicates that the service may provide the elements of the list 81 | // in any arbitrary order, rather than the order the user originally 82 | // provided. Additionally, the list's order may or may not be stable. 83 | UNORDERED_LIST = 6; 84 | } 85 | -------------------------------------------------------------------------------- /servers/prometheusServer/server.go: -------------------------------------------------------------------------------- 1 | package prometheusServer 2 | 3 | /* 4 | import ( 5 | "github.com/deqode/GoArcc/config" 6 | "github.com/deqode/GoArcc/util/logger" 7 | "context" 8 | grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" 9 | "github.com/prometheus/client_golang/prometheus" 10 | "github.com/prometheus/client_golang/prometheus/promhttp" 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/reflection" 13 | "net/http" 14 | "os" 15 | "os/signal" 16 | "time" 17 | ) 18 | 19 | type PrometheusConfig struct { 20 | Registry *prometheus.Registry 21 | ServerMetrics *grpc_prometheus.ServerMetrics 22 | } 23 | 24 | func InitPromthesiusServer() *PrometheusConfig { 25 | // Create a metrics registry. 26 | registry := prometheus.NewRegistry() 27 | // Create some standard server metrics. 28 | grpcServerMetrics := grpc_prometheus.NewServerMetrics() 29 | registry.MustRegister(grpcServerMetrics) 30 | //CreateMetrics() 31 | return &PrometheusConfig{ 32 | Registry: registry, 33 | ServerMetrics: grpcServerMetrics, 34 | } 35 | } 36 | 37 | //Note: Prometheus will only work when all service will be registered with grpc already 38 | func PrometheusRunner(config *config.Config, grpcServer *grpc.Server, prometheusConfig *PrometheusConfig) error { 39 | go func() { 40 | _ = RunPrometheusServer(context.Background(), config, grpcServer, prometheusConfig) 41 | }() 42 | return nil 43 | } 44 | 45 | // Started the prometheus server 46 | func RunPrometheusServer(ctxhelper context.Context, config *config.Config, grpcServer *grpc.Server, prometheusConfig *PrometheusConfig) error { 47 | ctxhelper, cancel := context.WithCancel(ctxhelper) 48 | defer cancel() 49 | 50 | srv := &http.Server{ 51 | Addr: config.Promthesius.Host + ":" + config.Promthesius.Port, 52 | Handler: promhttp.HandlerFor(prometheusConfig.Registry, promhttp.HandlerOpts{}), 53 | } 54 | 55 | reflection.Register(grpcServer) 56 | //initializing metrics 57 | prometheusConfig.ServerMetrics.InitializeMetrics(grpcServer) 58 | grpc_prometheus.Register(grpcServer) 59 | grpc_prometheus.EnableHandlingTimeHistogram() 60 | http.Handle("/metrics", promhttp.Handler()) 61 | // graceful shutdown 62 | c := make(chan os.Signal, 1) 63 | signal.Notify(c, os.Interrupt) 64 | go func() { 65 | for range c { 66 | // sig is a ^C, handle it 67 | } 68 | _, cancel := context.WithTimeout(ctxhelper, 30*time.Second) 69 | defer cancel() 70 | logger.Log.Info("graceful shutting down promthesius Server") 71 | _ = srv.Shutdown(ctxhelper) 72 | }() 73 | 74 | logger.Log.Info("starting prometheus server...") 75 | return srv.ListenAndServe() 76 | } 77 | 78 | type PrometheusMetrics struct { 79 | HitsTotal prometheus.Counter 80 | Hits *prometheus.CounterVec 81 | Times *prometheus.HistogramVec 82 | } 83 | 84 | func CreateMetrics() (*PrometheusMetrics, error) { 85 | var metr PrometheusMetrics 86 | metr.HitsTotal = prometheus.NewCounter(prometheus.CounterOpts{ 87 | Name: "_hits_total", 88 | }) 89 | if err := prometheus.Register(metr.HitsTotal); err != nil { 90 | return nil, err 91 | } 92 | metr.Hits = prometheus.NewCounterVec( 93 | prometheus.CounterOpts{ 94 | Name: "_hits", 95 | }, 96 | []string{"status", "method", "path"}, 97 | ) 98 | if err := prometheus.Register(metr.Hits); err != nil { 99 | return nil, err 100 | } 101 | metr.Times = prometheus.NewHistogramVec( 102 | prometheus.HistogramOpts{ 103 | Name: "_times", 104 | }, 105 | []string{"status", "method", "path"}, 106 | ) 107 | if err := prometheus.Register(metr.Times); err != nil { 108 | return nil, err 109 | } 110 | if err := prometheus.Register(prometheus.NewBuildInfoCollector()); err != nil { 111 | return nil, err 112 | } 113 | return &metr, nil 114 | } 115 | */ 116 | -------------------------------------------------------------------------------- /modules/authentication/v1/external-svc/login_callback.go: -------------------------------------------------------------------------------- 1 | package external_svc 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/coreos/go-oidc" 7 | "github.com/deqode/GoArcc/modules/authentication/v1/pb" 8 | userprofileModel "github.com/deqode/GoArcc/modules/user-profile/v1/models" 9 | userProfilePb "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 10 | "github.com/deqode/GoArcc/protos/types" 11 | "google.golang.org/grpc/codes" 12 | "google.golang.org/grpc/status" 13 | "gorm.io/gorm" 14 | "time" 15 | ) 16 | 17 | const tokenKey = "id_token" 18 | 19 | func (s *authenticationServer) LoginCallback(ctx context.Context, in *pb.LoginCallbackRequest) (*pb.LoginCallbackResponse, error) { 20 | if in == nil { 21 | return nil, status.Error(codes.FailedPrecondition, "Request is Nil") 22 | } 23 | if err := in.Validate(); err != nil { 24 | return nil, err 25 | } 26 | token, err := s.authenticator.Config.Exchange(ctx, in.Code) 27 | if err != nil { 28 | return nil, status.Errorf(codes.Unauthenticated, err.Error()) 29 | } 30 | 31 | rawIDToken, ok := token.Extra(tokenKey).(string) 32 | if !ok { 33 | return nil, status.Error(codes.Internal, "No id_token field in oauth2 token") 34 | } 35 | 36 | oidcConfig := &oidc.Config{ 37 | ClientID: s.config.Auth.Auth0ClientID, 38 | } 39 | 40 | idToken, err := s.authenticator.Provider.Verifier(oidcConfig).Verify(ctx, rawIDToken) 41 | if err != nil { 42 | return nil, status.Error(codes.Internal, "Failed to verify ID Token") 43 | } 44 | 45 | // Getting now the userInfo 46 | var profile map[string]interface{} 47 | if err := idToken.Claims(&profile); err != nil { 48 | return nil, status.Error(codes.Internal, err.Error()) 49 | } 50 | 51 | //get user_details 52 | var userId string 53 | usr, err := s.userProfileServer.GetUserProfileBySub(ctx, &userProfilePb.GetUserProfileBySubRequest{ 54 | Sub: fmt.Sprintf("%s", profile["sub"]), 55 | }) 56 | if usr != nil { 57 | return &pb.LoginCallbackResponse{ 58 | IdToken: rawIDToken, 59 | AccessToken: token.AccessToken, 60 | UserId: usr.Id, 61 | }, nil 62 | } 63 | if err != nil { 64 | if err != gorm.ErrRecordNotFound { 65 | return nil, err 66 | } 67 | } 68 | 69 | userId, err = s.CreateUserAndAccount(ctx, profile) 70 | if err != nil { 71 | return nil, err 72 | } 73 | return &pb.LoginCallbackResponse{ 74 | IdToken: rawIDToken, 75 | AccessToken: token.AccessToken, 76 | UserId: userId, 77 | }, nil 78 | } 79 | 80 | func (s *authenticationServer) CreateUserAndAccount(ctx context.Context, profile map[string]interface{}) (string, error) { 81 | gormDb := s.db 82 | userid := "" 83 | err := gormDb.Transaction(func(transaction *gorm.DB) error { 84 | usr := struct { 85 | ID string 86 | Name string 87 | UserName string 88 | Sub string 89 | ProfilePicURL string 90 | Source types.VCSProviders 91 | }{ 92 | ID: fmt.Sprintf("%s", profile["sub"]), 93 | Name: fmt.Sprintf("%s", profile["name"]), 94 | UserName: fmt.Sprintf("%s", profile["nickname"]), 95 | Sub: fmt.Sprintf("%s", profile["sub"]), 96 | ProfilePicURL: fmt.Sprintf("%s", profile["picture"]), 97 | Source: types.VCSProviders_GITHUB, 98 | } 99 | userProfileModel := userprofileModel.UserProfile{ 100 | ID: usr.ID, 101 | Name: usr.Name, 102 | UserName: usr.UserName, 103 | Sub: usr.Sub, 104 | ProfilePicURL: usr.ProfilePicURL, 105 | Source: usr.Source, 106 | CreatedAt: time.Time{}, 107 | UpdatedAt: time.Time{}, 108 | DeletedAt: gorm.DeletedAt{}, 109 | } 110 | tx := transaction.Create(&userProfileModel) 111 | if tx.Error != nil { 112 | return tx.Error 113 | } 114 | 115 | //if everything goes right then the transaction will commit by itself 116 | userid = usr.ID 117 | return nil 118 | }) 119 | //when transaction does not happen for some internal reason 120 | if err != nil { 121 | return "", status.Error(codes.Internal, err.Error()) 122 | } 123 | return userid, nil 124 | } 125 | -------------------------------------------------------------------------------- /logger/go.sum: -------------------------------------------------------------------------------- 1 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 2 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 7 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 8 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 9 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 10 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 11 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 12 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 16 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 17 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 18 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 19 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 20 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= 21 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 22 | go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= 23 | go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= 24 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= 25 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 26 | go.uber.org/zap v1.18.1 h1:CSUJ2mjFszzEWt4CdKISEuChVIXGBn3lAPwkRGyVrc4= 27 | go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= 28 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 29 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= 30 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 31 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 32 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 33 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 34 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 35 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 36 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 37 | golang.org/x/tools v0.0.0-20191108193012-7d206e10da11 h1:Yq9t9jnGoR+dBuitxdo9l6Q7xh/zOyNnYUtDKaQ3x0E= 38 | golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 39 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 40 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 41 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 42 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 43 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 44 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 45 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 46 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 47 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 48 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 49 | -------------------------------------------------------------------------------- /util/ctxhelper/go.sum: -------------------------------------------------------------------------------- 1 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 2 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 7 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 8 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 9 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 10 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 11 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 12 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 16 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 17 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 18 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 19 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 20 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= 21 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 22 | go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= 23 | go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= 24 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= 25 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 26 | go.uber.org/zap v1.18.1 h1:CSUJ2mjFszzEWt4CdKISEuChVIXGBn3lAPwkRGyVrc4= 27 | go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= 28 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 29 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= 30 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 31 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 32 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 33 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 34 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 35 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 36 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 37 | golang.org/x/tools v0.0.0-20191108193012-7d206e10da11 h1:Yq9t9jnGoR+dBuitxdo9l6Q7xh/zOyNnYUtDKaQ3x0E= 38 | golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 39 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 40 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 41 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 42 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 43 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 44 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 45 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 46 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 47 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 48 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 49 | -------------------------------------------------------------------------------- /modules/user-profile/v1/internal-svc/create_user_profile_test.go: -------------------------------------------------------------------------------- 1 | package internal_svc_test 2 | 3 | import ( 4 | "context" 5 | "github.com/deqode/GoArcc/modules/user-profile/v1/pb" 6 | "github.com/deqode/GoArcc/protos/types" 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | "google.golang.org/grpc/codes" 10 | "google.golang.org/grpc/status" 11 | ) 12 | 13 | var _ = Describe("CreateUserProfile", func() { 14 | var ( 15 | userProfileServer pb.UserProfileInternalServer 16 | profile *pb.UserProfile 17 | ) 18 | 19 | // this block will run after each it block 20 | BeforeEach(func() { 21 | userProfileServer = UserProfileServerIntTest 22 | profile = UsrProfile 23 | }) 24 | 25 | Describe("Creating an user profile", func() { 26 | //Negative Test Cases 27 | Context("Get an error when nil User provided", func() { 28 | It("should return nil exception", func() { 29 | _, err := userProfileServer.CreateUserProfile(context.Background(), &pb.CreateUserProfileRequest{UserProfile: nil}) 30 | Expect(err).Should(Equal(status.Error(codes.FailedPrecondition, "UserProfile not provided"))) 31 | }) 32 | }) 33 | 34 | Context("Create a user when subject is empty", func() { 35 | It("It should return validation error", func() { 36 | 37 | }) 38 | }) 39 | 40 | Context("Get an error when user created with wrong email", func() { 41 | It("if email is not provided then ignore because its a non required field", func() {}) 42 | It("Should return validation error when email is provided but not formatted", func() {}) 43 | }) 44 | 45 | Context("Get an error when user created with wrong Phone", func() { 46 | It("if phone is not provided then ignore because its a non required field", func() {}) 47 | It("Should return validation error when phone number is not in proper", func() {}) 48 | }) 49 | 50 | Context("Get an error when name of user not provided", func() { 51 | It("if name is nil", func() {}) 52 | It("if name length exceed maximum upto 100 character", func() {}) 53 | }) 54 | 55 | Context("Get an error when user-name of user not provided", func() { 56 | It("if user-name is empty return an error", func() {}) 57 | It("if user-name length exceed maximum upto 100 character return error", func() {}) 58 | }) 59 | 60 | Context("Return proper error when user is creating from unknown source", func() { 61 | It("should return error if user is from unknown source", func() { 62 | _, err := userProfileServer.CreateUserProfile(context.Background(), &pb.CreateUserProfileRequest{UserProfile: &pb.UserProfile{ 63 | Id: profile.Id, 64 | Sub: profile.Sub, 65 | Name: profile.Name, 66 | UserName: profile.UserName, 67 | Email: profile.Email, 68 | PhoneNumber: profile.PhoneNumber, 69 | ExternalSource: types.VCSProviders_UNKNOWN, 70 | ProfilePicUrl: profile.ProfilePicUrl, 71 | TokenValidTill: profile.TokenValidTill, 72 | }}) 73 | Expect(err.(pb.CreateUserProfileRequestValidationError).Cause().(pb.UserProfileValidationError).Reason()).Should(Equal("value must not be in list [0]")) 74 | }) 75 | }) 76 | 77 | Context("Get an error when wrong profile-address provided", func() { 78 | It("if not a valid url return error", func() {}) 79 | 80 | }) 81 | 82 | //Positive Test Cases 83 | Context("Create a user profile", func() { 84 | It("should return user_id as uuid", func() { 85 | _, err := userProfileServer.CreateUserProfile(context.Background(), &pb.CreateUserProfileRequest{UserProfile: &pb.UserProfile{ 86 | Id: profile.Id + "test", 87 | Sub: profile.Sub + "test", 88 | Name: profile.Name, 89 | UserName: profile.UserName, 90 | Email: profile.Email, 91 | PhoneNumber: profile.PhoneNumber, 92 | ExternalSource: profile.ExternalSource, 93 | ProfilePicUrl: profile.ProfilePicUrl, 94 | TokenValidTill: profile.TokenValidTill, 95 | }}) 96 | Expect(err).To(BeNil(), "Error") 97 | }) 98 | }) 99 | 100 | Context("Get an error when user create with already existing sub", func() { 101 | It("should return already exists error", func() { 102 | request := profile 103 | _, err := userProfileServer.CreateUserProfile(context.Background(), &pb.CreateUserProfileRequest{UserProfile: request}) 104 | Expect(err).To(Not(BeNil()), "Error") 105 | }) 106 | }) 107 | 108 | }) 109 | }) 110 | -------------------------------------------------------------------------------- /protos/types/enums.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.26.0 4 | // protoc v3.6.1 5 | // source: enums.proto 6 | 7 | package types 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type VCSProviders int32 24 | 25 | const ( 26 | VCSProviders_UNKNOWN VCSProviders = 0 27 | VCSProviders_GITHUB VCSProviders = 1 28 | VCSProviders_GITLAB VCSProviders = 2 29 | VCSProviders_BITBUCKET VCSProviders = 3 30 | ) 31 | 32 | // Enum value maps for VCSProviders. 33 | var ( 34 | VCSProviders_name = map[int32]string{ 35 | 0: "UNKNOWN", 36 | 1: "GITHUB", 37 | 2: "GITLAB", 38 | 3: "BITBUCKET", 39 | } 40 | VCSProviders_value = map[string]int32{ 41 | "UNKNOWN": 0, 42 | "GITHUB": 1, 43 | "GITLAB": 2, 44 | "BITBUCKET": 3, 45 | } 46 | ) 47 | 48 | func (x VCSProviders) Enum() *VCSProviders { 49 | p := new(VCSProviders) 50 | *p = x 51 | return p 52 | } 53 | 54 | func (x VCSProviders) String() string { 55 | return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 56 | } 57 | 58 | func (VCSProviders) Descriptor() protoreflect.EnumDescriptor { 59 | return file_enums_proto_enumTypes[0].Descriptor() 60 | } 61 | 62 | func (VCSProviders) Type() protoreflect.EnumType { 63 | return &file_enums_proto_enumTypes[0] 64 | } 65 | 66 | func (x VCSProviders) Number() protoreflect.EnumNumber { 67 | return protoreflect.EnumNumber(x) 68 | } 69 | 70 | // Deprecated: Use VCSProviders.Descriptor instead. 71 | func (VCSProviders) EnumDescriptor() ([]byte, []int) { 72 | return file_enums_proto_rawDescGZIP(), []int{0} 73 | } 74 | 75 | var File_enums_proto protoreflect.FileDescriptor 76 | 77 | var file_enums_proto_rawDesc = []byte{ 78 | 0x0a, 0x0b, 0x65, 0x6e, 0x75, 0x6d, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x61, 79 | 0x6c, 0x66, 0x72, 0x65, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2a, 0x42, 0x0a, 0x0c, 0x56, 80 | 0x43, 0x53, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 81 | 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x47, 0x49, 0x54, 0x48, 82 | 0x55, 0x42, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x47, 0x49, 0x54, 0x4c, 0x41, 0x42, 0x10, 0x02, 83 | 0x12, 0x0d, 0x0a, 0x09, 0x42, 0x49, 0x54, 0x42, 0x55, 0x43, 0x4b, 0x45, 0x54, 0x10, 0x03, 0x42, 84 | 0x15, 0x5a, 0x13, 0x61, 0x6c, 0x66, 0x72, 0x65, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 85 | 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 86 | } 87 | 88 | var ( 89 | file_enums_proto_rawDescOnce sync.Once 90 | file_enums_proto_rawDescData = file_enums_proto_rawDesc 91 | ) 92 | 93 | func file_enums_proto_rawDescGZIP() []byte { 94 | file_enums_proto_rawDescOnce.Do(func() { 95 | file_enums_proto_rawDescData = protoimpl.X.CompressGZIP(file_enums_proto_rawDescData) 96 | }) 97 | return file_enums_proto_rawDescData 98 | } 99 | 100 | var file_enums_proto_enumTypes = make([]protoimpl.EnumInfo, 1) 101 | var file_enums_proto_goTypes = []interface{}{ 102 | (VCSProviders)(0), // 0: goarcc.types.VCSProviders 103 | } 104 | var file_enums_proto_depIdxs = []int32{ 105 | 0, // [0:0] is the sub-list for method output_type 106 | 0, // [0:0] is the sub-list for method input_type 107 | 0, // [0:0] is the sub-list for extension type_name 108 | 0, // [0:0] is the sub-list for extension extendee 109 | 0, // [0:0] is the sub-list for field type_name 110 | } 111 | 112 | func init() { file_enums_proto_init() } 113 | func file_enums_proto_init() { 114 | if File_enums_proto != nil { 115 | return 116 | } 117 | type x struct{} 118 | out := protoimpl.TypeBuilder{ 119 | File: protoimpl.DescBuilder{ 120 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 121 | RawDescriptor: file_enums_proto_rawDesc, 122 | NumEnums: 1, 123 | NumMessages: 0, 124 | NumExtensions: 0, 125 | NumServices: 0, 126 | }, 127 | GoTypes: file_enums_proto_goTypes, 128 | DependencyIndexes: file_enums_proto_depIdxs, 129 | EnumInfos: file_enums_proto_enumTypes, 130 | }.Build() 131 | File_enums_proto = out.File 132 | file_enums_proto_rawDesc = nil 133 | file_enums_proto_goTypes = nil 134 | file_enums_proto_depIdxs = nil 135 | } 136 | -------------------------------------------------------------------------------- /util/userinfo/userinfo.go: -------------------------------------------------------------------------------- 1 | package userinfo 2 | 3 | import ( 4 | "context" 5 | "google.golang.org/grpc/codes" 6 | "google.golang.org/grpc/status" 7 | "gorm.io/gorm" 8 | "time" 9 | ) 10 | 11 | type UserInfo struct { 12 | ID string 13 | Email string 14 | Sub string 15 | TokenExpiry time.Time 16 | } 17 | 18 | type ValidateUserInfo struct { 19 | Ctx context.Context 20 | RootTable interface{} 21 | RootTableTag string 22 | //Key Will be tag name 23 | Args map[string]interface{} 24 | SkipRootValidation bool 25 | SkipArgsValidation bool 26 | Db *gorm.DB 27 | skipValidation bool 28 | } 29 | 30 | const ( 31 | userKey key = iota 32 | clientIpKey 33 | grpcCallKey 34 | ) 35 | 36 | type key int 37 | 38 | func GetClientIP(ctx context.Context) string { 39 | if ip, ok := ctx.Value(clientIpKey).(string); ok { 40 | return ip 41 | } 42 | return "" 43 | } 44 | 45 | func NewContextWithClientIP(ctx context.Context, ip string) context.Context { 46 | return context.WithValue(ctx, clientIpKey, ip) 47 | } 48 | 49 | func NewContextForGrpcCall(ctx context.Context) context.Context { 50 | return context.WithValue(ctx, grpcCallKey, true) 51 | } 52 | 53 | func IsGrpcCall(ctx context.Context) bool { 54 | if v, ok := ctx.Value(grpcCallKey).(bool); ok { 55 | return v 56 | } 57 | return false 58 | } 59 | 60 | func WithClaims(ctx context.Context, c map[string]interface{}) context.Context { 61 | return context.WithValue(ctx, userKey, c) 62 | } 63 | 64 | // FromContext returns the User value stored in ctx, if any. 65 | func FromContext(ctx context.Context) (usr UserInfo) { 66 | usr = FromClaims(ctx.Value(userKey).(map[string]interface{})) 67 | return 68 | } 69 | 70 | func NewContext(ctx context.Context, u UserInfo) context.Context { 71 | m := make(map[string]interface{}) 72 | m["sub"] = u.Sub 73 | return context.WithValue(ctx, userKey, m) 74 | } 75 | 76 | func FromClaims(claims map[string]interface{}) (ui UserInfo) { 77 | if claims == nil { 78 | return 79 | } 80 | 81 | if v, ok := claims["sub"]; ok { 82 | ui.Sub = v.(string) 83 | ui.ID = v.(string) 84 | } 85 | if v, ok := claims["ext"]; ok { 86 | if e, exist := v.(map[string]interface{})["email"]; exist { 87 | ui.Email = e.(string) 88 | } 89 | } 90 | 91 | if v, ok := claims["exp"]; ok { 92 | tm := time.Unix(int64(v.(float64)), 0) 93 | ui.TokenExpiry = tm 94 | } 95 | 96 | return 97 | } 98 | 99 | func (validateUserInfo *ValidateUserInfo) validate() error { 100 | if validateUserInfo == nil { 101 | return status.Error(codes.FailedPrecondition, "authentication validation failed") 102 | } 103 | if !validateUserInfo.SkipRootValidation && (validateUserInfo.RootTableTag == "" || validateUserInfo.RootTable == nil) { 104 | return status.Error(codes.FailedPrecondition, "authentication validation failed") 105 | } 106 | if !validateUserInfo.SkipArgsValidation && validateUserInfo.Args == nil { 107 | return status.Error(codes.FailedPrecondition, "authentication validation failed") 108 | } 109 | return nil 110 | } 111 | 112 | // BasicAuthValidation It will check the user id present in context is valid or not 113 | func BasicAuthValidation(ctx context.Context, db *gorm.DB) error { 114 | v := &ValidateUserInfo{ 115 | Ctx: ctx, 116 | //RootTable: &models.UserProfile{}, 117 | RootTableTag: "id", 118 | Args: nil, 119 | SkipRootValidation: false, 120 | SkipArgsValidation: true, 121 | Db: db, 122 | skipValidation: true, 123 | } 124 | if err := v.ValidateUser(); err != nil { 125 | return err 126 | } 127 | return nil 128 | } 129 | 130 | // ValidateUser Validate User : Will validate user id is present in the given table or not 131 | func (validateUserInfo *ValidateUserInfo) ValidateUser() error { 132 | if !validateUserInfo.skipValidation { 133 | if err := validateUserInfo.validate(); err != nil { 134 | return err 135 | } 136 | } 137 | //Extract user id from context 138 | userInfo := FromContext(validateUserInfo.Ctx) 139 | if userInfo.Sub == "" { 140 | return status.Error(codes.PermissionDenied, "unauthenticated user") 141 | } 142 | if !validateUserInfo.SkipRootValidation { 143 | tx := validateUserInfo.Db.Where(validateUserInfo.RootTableTag+" = ?", userInfo.ID).First(validateUserInfo.RootTable) 144 | if tx.Error != nil { 145 | return status.Error(codes.PermissionDenied, "unauthenticated user") 146 | } 147 | } 148 | 149 | if !validateUserInfo.SkipArgsValidation { 150 | for tag, argTable := range validateUserInfo.Args { 151 | tx := validateUserInfo.Db.Where(tag+" = ?", userInfo.ID).First(argTable) 152 | if tx.Error != nil { 153 | return status.Error(codes.PermissionDenied, "unauthenticated user") 154 | } 155 | } 156 | } 157 | return nil 158 | } 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoArcc - Go monolith with embedded microservices including GRPC,REST, graphQL and The Clean Architecture. 2 | 3 |

4 | 5 | ![GitHub](https://img.shields.io/github/license/deqode/GoArcc) 6 | [![CI](https://github.com/deqode/GoArcc/actions/workflows/go.yml/badge.svg)](https://github.com/deqode/GoArcc/actions/workflows/go.yml) 7 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/deqode/GoArcc)](https://pkg.go.dev/github.com/github.com/deqode/GoArcc) 8 | [![Go Report Card](https://goreportcard.com/badge/github.com/deqode/GoArcc)](https://goreportcard.com/report/github.com/deqode/GoArcc) 9 | [![Project Layout](https://img.shields.io/badge/Standard%20Go-Project%20Layout-informational)](https://github.com/golang-standards/project-layout) 10 | ![GitHub last commit](https://img.shields.io/github/last-commit/deqode/codeanalyser) 11 | ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/deqode/GoArcc) 12 | 13 | ## Description 14 | When you start writing a Go project, GoArcc helps to set up all the initial code boilerplate for your project. Initial boilerplate code means how you should organize your codebase and how you can write multiple services. We have support for REST, Graphql as well as gRPC. 15 | 16 | It supports logging, tracing, health check, Jaeger, etc so that any developer will come and write services within a minute. 17 | 18 | In short, GoArcc is a boilerplate setup codebase for any monolithic(Architecture) based web/mobile applications which later converted into microservices(Architecture). 19 | 20 | [Read more about GoArcc](https://deqode.github.io/GoArcc/) 21 | 22 | ## Structure of Go packages 23 | 24 | - `client/*` - clients for server dialing 25 | - `grpcClient` - grpcClient dials grpc server 26 | - `cmd/*` - main application(s) 27 | - `config` - application related configs 28 | - `db` - postgres DB connection and adapters 29 | - `logger` - global zap logger 30 | - `modules/*` - embedded microservices, with structure: 31 | - `external-svc` - exposed apis logic implementation 32 | - `internal-svc` - unexposed apis logic implementation 33 | - `models` - database models, operations using gorm 34 | - `pb` - autogenerated files from .proto file 35 | - `protos` - External required protos for internal protos 36 | - `types` - application related common proto types 37 | - `servers` - all running servers 38 | - `graphql` - ms graphql registration and server invoke 39 | - `grpc` - ms grpc registration and server invoke 40 | - `rest` - ms rest registration and server invoke 41 | ## Features 42 | 43 | - [X] Project structure (mostly) follows 44 | [Standard Go Project Layout](https://github.com/golang-standards/project-layout). 45 | - [X] Easily testable code (thanks to The Clean Architecture). 46 | - [X] Graceful shutdown support. 47 | - [X] Example gRPC API: 48 | - [X] External and internal APIs on different host/port. 49 | - [X] gRPC services with and without token-based authentication. 50 | - [X] API design (mostly) follows 51 | [Google API Design Guide](https://cloud.google.com/apis/design) and 52 | [Google API Improvement Proposals](https://google.aip.dev/). 53 | - [X] Example graphQL API: 54 | - [X] Example REST API: 55 | - [X] Example tests, both unit and integration. 56 | - [X] Production logging using [zap](https://github.com/uber-go/zap). 57 | - [X] Production metrics using Prometheus. 58 | - [X] Docker and docker-compose support. 59 | - [X] Smart test coverage report 60 | - [X] CI/CD setup for GitHub Actions. 61 | 62 | ## Development 63 | 64 | ### Requirements 65 | 66 | - Go 1.16 67 | - [Docker](https://docs.docker.com/install/) 19.03+ 68 | - [Docker Compose](https://docs.docker.com/compose/install/) 1.25+ 69 | 70 | ### Build from source 71 | 72 | 1. Clone the repo: 73 | ``` 74 | git clone git@github.com:deqode/GoArcc.git 75 | ``` 76 | 2. After cloning the repo, review `config.yml` and update for your system as needed 77 | 3. Build to create GoArcc binary 78 | ``` 79 | make build 80 | ``` 81 | 4. Run unit tests with 82 | ``` 83 | make test 84 | ``` 85 | 86 | 87 | ## Run 88 | You can find all the running servers at: 89 | 90 | **Jaeger UI:** 91 | 92 | http://localhost:16686 93 | 94 | **Health Trace:** 95 | 96 | http://localhost:8083/health/ 97 | 98 | **Prometheus UI:** 99 | 100 | http://localhost:9090 101 | 102 | **Prometheus UI Metrics:** 103 | 104 | http://localhost:9090/metrics 105 | 106 | **Grpc Server:** 107 | 108 | http://localhost:8080 109 | 110 | **Graphql Server:** 111 | 112 | http://localhost:8081 113 | 114 | **Rest Server:** 115 | 116 | http://localhost:8082 117 | 118 | 119 | ## License 120 | ``` 121 | Copyright 2021, DeqodeLabs (https://deqode.com/) 122 | 123 | Licensed under the MIT License(the "License"); 124 | 125 | ``` 126 |

127 | -------------------------------------------------------------------------------- /servers/grpc/middleware/auth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "github.com/justinas/alice" 7 | "go.uber.org/fx" 8 | "google.golang.org/grpc" 9 | "google.golang.org/grpc/codes" 10 | "google.golang.org/grpc/metadata" 11 | "google.golang.org/grpc/status" 12 | "gopkg.in/square/go-jose.v2" 13 | "gopkg.in/square/go-jose.v2/jwt" 14 | "net/http" 15 | "strings" 16 | "time" 17 | 18 | "github.com/deqode/GoArcc/util/userinfo" 19 | ) 20 | 21 | const ( 22 | id = "id" 23 | email = "email" 24 | ) 25 | 26 | type Config struct { 27 | Issuer string 28 | Secret string 29 | Domain string 30 | Audience []string 31 | } 32 | 33 | // NewConfig todo : keep this config into config-local 34 | // todo: Move these base AUth0 URLs to config 35 | func NewConfig() (Config, error) { 36 | newConfig := Config{ 37 | Issuer: "https://alfred-sh.us.auth0.com/", 38 | Secret: "secret", 39 | Domain: "https://alfred-sh.us.auth0.com", 40 | Audience: []string{"BFnfdaibKSdqkSAOksr3XuUNJuCW9zbZ"}, 41 | } 42 | 43 | return newConfig, nil 44 | } 45 | 46 | func AuthMiddleware(ctx context.Context) (context.Context, error) { 47 | authRequired := true 48 | //getting authentication from the context . ie bearer 49 | md, ok := metadata.FromIncomingContext(ctx) 50 | if !ok { 51 | return nil, status.Error(codes.FailedPrecondition, "authorization failed") 52 | } 53 | //get method name 54 | operationName, _ := grpc.Method(ctx) 55 | //check for public end point 56 | for _, v := range publicEndpoint { 57 | if operationName == v { 58 | authRequired = false 59 | break 60 | } 61 | } 62 | 63 | if authRequired { 64 | token := md.Get("authorization") 65 | if len(token) < 1 { 66 | return nil, status.Error(codes.FailedPrecondition, "Authorization header missing") 67 | } 68 | splitToken := strings.Split(token[0], " ") 69 | if len(splitToken) < 2 { 70 | return nil, status.Error(codes.FailedPrecondition, "Bearer token not found.") 71 | } 72 | jwk, err := NewJwk() 73 | if err != nil { 74 | return nil, err 75 | } 76 | _, err = NewJWtMiddleware(jwk.JSONWebKeySet, splitToken[1]) 77 | if err != nil { 78 | return nil, err 79 | } 80 | ctx, err = NewJWTUnaryInterceptor(ctx, jwk.JSONWebKeySet, splitToken[1]) 81 | if err != nil { 82 | return nil, err 83 | } 84 | } 85 | return ctx, nil 86 | } 87 | 88 | type DiscoveryResponse struct { 89 | fx.Out 90 | RevocationEndpoint string `name:"revocation_endpoint"` 91 | JSONWebKeySet *jose.JSONWebKeySet 92 | } 93 | 94 | // NewJwk NewJwk() get certificate from openIdConnect 95 | func NewJwk() (DiscoveryResponse, error) { 96 | c, _ := NewConfig() 97 | discoveryURI := strings.TrimSuffix(c.Issuer, "/") + "/.well-known/openid-configuration" 98 | 99 | client := http.Client{ 100 | Timeout: 10 * time.Second, 101 | } 102 | 103 | res, err := client.Get(discoveryURI) 104 | if err != nil { 105 | return DiscoveryResponse{}, err 106 | } 107 | defer res.Body.Close() 108 | 109 | var discoResp struct { 110 | JwkURI string `json:"jwks_uri"` 111 | RevocationEndpoint string `json:"revocation_endpoint"` 112 | } 113 | 114 | if err := json.NewDecoder(res.Body).Decode(&discoResp); err != nil { 115 | return DiscoveryResponse{}, err 116 | } 117 | 118 | res, err = client.Get(discoResp.JwkURI) 119 | if err != nil { 120 | return DiscoveryResponse{}, err 121 | } 122 | defer res.Body.Close() 123 | 124 | var keySet jose.JSONWebKeySet 125 | if err := json.NewDecoder(res.Body).Decode(&keySet); err != nil { 126 | return DiscoveryResponse{}, err 127 | } 128 | 129 | return DiscoveryResponse{ 130 | RevocationEndpoint: discoResp.RevocationEndpoint, 131 | JSONWebKeySet: &keySet, 132 | }, nil 133 | } 134 | 135 | // NewJWtMiddleware verify the user's token 136 | func NewJWtMiddleware(ks *jose.JSONWebKeySet, token string) (alice.Constructor, error) { 137 | c, _ := NewConfig() 138 | _, err := VerifyToken(token, c.Issuer, c.Audience, ks) 139 | if err != nil { 140 | return nil, status.Error(codes.Unauthenticated, "Unable to verify User") 141 | } 142 | return nil, nil 143 | } 144 | 145 | // NewJWTUnaryInterceptor add users claims to outgoing context 146 | func NewJWTUnaryInterceptor(ctx context.Context, ks *jose.JSONWebKeySet, token string) (context.Context, error) { 147 | c, _ := NewConfig() 148 | claims, err := VerifyToken(token, c.Issuer, c.Audience, ks) 149 | if err != nil { 150 | return nil, status.Error(codes.Unauthenticated, "Unable to verify User") 151 | } 152 | 153 | ui := userinfo.FromClaims(claims) 154 | if ui.ID != "" { 155 | ctx = metadata.AppendToOutgoingContext(ctx, id, ui.ID, email, ui.Email) 156 | } 157 | return userinfo.WithClaims(ctx, claims), nil 158 | } 159 | 160 | func VerifyToken(t, iss string, aud []string, ks *jose.JSONWebKeySet) (map[string]interface{}, error) { 161 | token, err := jwt.ParseSigned(t) 162 | if err != nil { 163 | return nil, err 164 | } 165 | 166 | claims := jwt.Claims{} 167 | claimMap := make(map[string]interface{}) 168 | err = token.Claims(ks, &claims, &claimMap) 169 | if err != nil { 170 | return nil, err 171 | } 172 | if err := claims.Validate(jwt.Expected{ 173 | Issuer: iss, 174 | Time: time.Now(), 175 | Audience: aud, 176 | }); err != nil { 177 | return nil, err 178 | } 179 | 180 | return claimMap, nil 181 | } 182 | -------------------------------------------------------------------------------- /modules/user-profile/v1/pb/user_profile_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | 3 | package pb 4 | 5 | import ( 6 | context "context" 7 | empty "github.com/golang/protobuf/ptypes/empty" 8 | grpc "google.golang.org/grpc" 9 | codes "google.golang.org/grpc/codes" 10 | status "google.golang.org/grpc/status" 11 | ) 12 | 13 | // This is a compile-time assertion to ensure that this generated file 14 | // is compatible with the grpc package it is being compiled against. 15 | const _ = grpc.SupportPackageIsVersion7 16 | 17 | // UserProfilesClient is the client API for UserProfiles service. 18 | // 19 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 20 | type UserProfilesClient interface { 21 | // GetUserProfile return a profile of a user 22 | GetUserProfile(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*UserProfile, error) 23 | //GetUserProfileBySub return a user profile by its unique sub provided by vcs 24 | GetUserProfileBySub(ctx context.Context, in *GetUserProfileBySubRequest, opts ...grpc.CallOption) (*UserProfile, error) 25 | } 26 | 27 | type userProfilesClient struct { 28 | cc grpc.ClientConnInterface 29 | } 30 | 31 | func NewUserProfilesClient(cc grpc.ClientConnInterface) UserProfilesClient { 32 | return &userProfilesClient{cc} 33 | } 34 | 35 | func (c *userProfilesClient) GetUserProfile(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*UserProfile, error) { 36 | out := new(UserProfile) 37 | err := c.cc.Invoke(ctx, "/goarcc.user_profile.v1.UserProfiles/GetUserProfile", in, out, opts...) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return out, nil 42 | } 43 | 44 | func (c *userProfilesClient) GetUserProfileBySub(ctx context.Context, in *GetUserProfileBySubRequest, opts ...grpc.CallOption) (*UserProfile, error) { 45 | out := new(UserProfile) 46 | err := c.cc.Invoke(ctx, "/goarcc.user_profile.v1.UserProfiles/GetUserProfileBySub", in, out, opts...) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return out, nil 51 | } 52 | 53 | // UserProfilesServer is the server API for UserProfiles service. 54 | // All implementations should embed UnimplementedUserProfilesServer 55 | // for forward compatibility 56 | type UserProfilesServer interface { 57 | // GetUserProfile return a profile of a user 58 | GetUserProfile(context.Context, *empty.Empty) (*UserProfile, error) 59 | //GetUserProfileBySub return a user profile by its unique sub provided by vcs 60 | GetUserProfileBySub(context.Context, *GetUserProfileBySubRequest) (*UserProfile, error) 61 | } 62 | 63 | // UnimplementedUserProfilesServer should be embedded to have forward compatible implementations. 64 | type UnimplementedUserProfilesServer struct { 65 | } 66 | 67 | func (UnimplementedUserProfilesServer) GetUserProfile(context.Context, *empty.Empty) (*UserProfile, error) { 68 | return nil, status.Errorf(codes.Unimplemented, "method GetUserProfile not implemented") 69 | } 70 | func (UnimplementedUserProfilesServer) GetUserProfileBySub(context.Context, *GetUserProfileBySubRequest) (*UserProfile, error) { 71 | return nil, status.Errorf(codes.Unimplemented, "method GetUserProfileBySub not implemented") 72 | } 73 | 74 | // UnsafeUserProfilesServer may be embedded to opt out of forward compatibility for this service. 75 | // Use of this interface is not recommended, as added methods to UserProfilesServer will 76 | // result in compilation errors. 77 | type UnsafeUserProfilesServer interface { 78 | mustEmbedUnimplementedUserProfilesServer() 79 | } 80 | 81 | func RegisterUserProfilesServer(s *grpc.Server, srv UserProfilesServer) { 82 | s.RegisterService(&_UserProfiles_serviceDesc, srv) 83 | } 84 | 85 | func _UserProfiles_GetUserProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 86 | in := new(empty.Empty) 87 | if err := dec(in); err != nil { 88 | return nil, err 89 | } 90 | if interceptor == nil { 91 | return srv.(UserProfilesServer).GetUserProfile(ctx, in) 92 | } 93 | info := &grpc.UnaryServerInfo{ 94 | Server: srv, 95 | FullMethod: "/goarcc.user_profile.v1.UserProfiles/GetUserProfile", 96 | } 97 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 98 | return srv.(UserProfilesServer).GetUserProfile(ctx, req.(*empty.Empty)) 99 | } 100 | return interceptor(ctx, in, info, handler) 101 | } 102 | 103 | func _UserProfiles_GetUserProfileBySub_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 104 | in := new(GetUserProfileBySubRequest) 105 | if err := dec(in); err != nil { 106 | return nil, err 107 | } 108 | if interceptor == nil { 109 | return srv.(UserProfilesServer).GetUserProfileBySub(ctx, in) 110 | } 111 | info := &grpc.UnaryServerInfo{ 112 | Server: srv, 113 | FullMethod: "/goarcc.user_profile.v1.UserProfiles/GetUserProfileBySub", 114 | } 115 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 116 | return srv.(UserProfilesServer).GetUserProfileBySub(ctx, req.(*GetUserProfileBySubRequest)) 117 | } 118 | return interceptor(ctx, in, info, handler) 119 | } 120 | 121 | var _UserProfiles_serviceDesc = grpc.ServiceDesc{ 122 | ServiceName: "goarcc.user_profile.v1.UserProfiles", 123 | HandlerType: (*UserProfilesServer)(nil), 124 | Methods: []grpc.MethodDesc{ 125 | { 126 | MethodName: "GetUserProfile", 127 | Handler: _UserProfiles_GetUserProfile_Handler, 128 | }, 129 | { 130 | MethodName: "GetUserProfileBySub", 131 | Handler: _UserProfiles_GetUserProfileBySub_Handler, 132 | }, 133 | }, 134 | Streams: []grpc.StreamDesc{}, 135 | Metadata: "pb/user_profile.proto", 136 | } 137 | -------------------------------------------------------------------------------- /protos/include/graphql.proto: -------------------------------------------------------------------------------- 1 | // graphql.proto 2 | // 3 | // Copyright (c) 2020 ysugimoto 4 | // 5 | // Released under the MIT license. 6 | // see https://opensource.org/licenses/MIT 7 | syntax = "proto3"; 8 | 9 | package graphql; 10 | 11 | option go_package = "github.com/ysugimoto/grpc-graphql-gateway/graphql"; 12 | 13 | import "google/protobuf/descriptor.proto"; 14 | 15 | // Extend ServiceOptions in order to define grpc connection setting. 16 | // User can use this option as following: 17 | // 18 | // service Greeter { 19 | // option (graphql.service) = { 20 | // host: "localhost:50051" // define grpc connection host and port 21 | // insecure: true // set true if connect to insecure grpc server 22 | // }; 23 | // 24 | // ... some rpc definitions 25 | // } 26 | message GraphqlService { 27 | // gRPC default connection host. 28 | // This value should include host and port, say localhost:50051. 29 | string host = 1; 30 | // If true, automatic connection with insecure option. 31 | bool insecure = 2; 32 | } 33 | 34 | 35 | // Extend MethodOptions in order to define GraphQL Query or Mutation. 36 | // User can use this option as following: 37 | // 38 | // service Greeter { 39 | // rpc SayHello(HelloRequest) returns (HelloReply) { 40 | // option (graphql.schema) = { 41 | // type: QUERY // declare as Query 42 | // name: "hello" // query name 43 | // } 44 | // } 45 | // } 46 | // 47 | // Since gRPC reason, it has limitation that the response could not be repeated. 48 | // it's dificcurl to respond array response, so that we accept "response.pluck" 49 | // in order to expose repeated fields in response message. 50 | // 51 | // For instance: 52 | // 53 | // message Member { 54 | // string name = 1; 55 | // } 56 | // 57 | // message ListMembersResponse { 58 | // repeated Member members = 1; -- could be array response 59 | // } 60 | // 61 | // message ListMembersRequest { 62 | // } 63 | // 64 | // service MemberService { 65 | // rpc ListMembers(ListMembersRequest) returns (ListMembersResponse) { 66 | // option (graphql.schema) = { 67 | // type: QUERY 68 | // name: "members" 69 | // response { 70 | // repeated : true 71 | // pluck: "members" // Query will respond [Member] instead of ListMembersResponse 72 | // } 73 | // } 74 | // } 75 | // } 76 | // 77 | // In mutation declaration: 78 | // 79 | // service MemberService { 80 | // rpc CreateMember(CreateMemberRequest) returns (Member) { 81 | // option (graphql.schema) = { 82 | // type: MUTATION // declare as Mutation 83 | // name: "cretemember" // mutation name 84 | // } 85 | // } 86 | // } 87 | // 88 | // The Mutation's input always becomes an input object, so you need to declare argument name. 89 | // 90 | // message Member { 91 | // string name = 1; 92 | // } 93 | // 94 | // message CreateMemberRequest { 95 | // string name = 1; 96 | // } 97 | // 98 | // service MemberService { 99 | // rpc CreateMember(CreateMemberRequest) returns (Member) { 100 | // option (graphql.schema) = { 101 | // type: MUTATION 102 | // name: "createmember" 103 | // request { 104 | // name: "member" // this is equivalent to createbook(member: Member): Member in GraphQL 105 | // } 106 | // } 107 | // } 108 | // } 109 | // 110 | // Finally, user can access this query via /graphql?query={members{name}} 111 | message GraphqlSchema { 112 | // graphql type. Enum of QUERY or MUTATION is valid value 113 | GraphqlType type = 1; 114 | // query name. this field is required 115 | string name = 2; 116 | // Query request object configuration 117 | GraphqlRequest request = 3; 118 | // Query response object configuration 119 | GraphqlResponse response = 4; 120 | } 121 | 122 | // configuration option for request 123 | message GraphqlRequest { 124 | // Define input name. 125 | // This field enables only for mutation and note that if this field is specified, 126 | // the gRPC request message will be dealt with an input. 127 | string name = 1; 128 | 129 | // Define pluck message fields 130 | repeated string plucks = 2; 131 | } 132 | 133 | // configuration option for response 134 | message GraphqlResponse { 135 | // If true, this response object is required 136 | // But when you declare "pluck", we respect expose field definition. 137 | bool required = 1; 138 | 139 | // Define pluck message field. 140 | // Note that this field IS NOT repeated, just single string field. 141 | // It means the response could only be single. 142 | string pluck = 2; 143 | } 144 | 145 | // explicit schema declaration enum 146 | enum GraphqlType { 147 | // schema will generate as Query 148 | QUERY = 0; 149 | // schema will generate as Mutation 150 | MUTATION = 1; 151 | // schema will generate as Resolver. Resolver behaves not listed in query, but can resolve nested field. 152 | RESOLVER = 2; 153 | } 154 | 155 | // GraphqlField is FieldOptions in protobuf in order to define type field attribute. 156 | // User can use this option as following: 157 | // 158 | // message Member { 159 | // string name = 1 [(graphql.field) = {required: true}]; // this field is required in GraphQL, it equivalent to String! on GraphQL 160 | // } 161 | // 162 | // message CreateMemberRequest { 163 | // string name = 1; [(grahpql.field) = {default: "anonymous"}]; // use default value on input or query 164 | // } 165 | // 166 | // Note that in protobuf, all fields are dealt with optional 167 | // so the same as it, all GraphQL fields are optional as default. 168 | // If you need to be required, use 'required: true' option 169 | message GraphqlField { 170 | // If true, this field is required. 171 | bool required = 1; 172 | // Use as other field name (not recommend) 173 | string name = 2; 174 | // Define default value on input. 175 | string default = 3; 176 | // Omit this field from graphql definition 177 | bool omit = 4; 178 | // Resolve this field by nested query with additional RPC 179 | string resolver = 5; 180 | } 181 | 182 | // Extend builtin messages 183 | 184 | extend google.protobuf.ServiceOptions { 185 | GraphqlService service = 1079; 186 | } 187 | 188 | extend google.protobuf.FieldOptions { 189 | GraphqlField field = 1079; 190 | } 191 | 192 | extend google.protobuf.MethodOptions { 193 | GraphqlSchema schema = 1079; 194 | } 195 | -------------------------------------------------------------------------------- /modules/authentication/v1/pb/pb.graphql.go: -------------------------------------------------------------------------------- 1 | // Code generated by proroc-gen-graphql, DO NOT EDIT. 2 | package pb 3 | 4 | import ( 5 | "github.com/graphql-go/graphql" 6 | ) 7 | 8 | var ( 9 | gql__type_ValidateUserLoginResponse *graphql.Object // message ValidateUserLoginResponse in pb/authentication.proto 10 | gql__type_ValidateUserLoginRequest *graphql.Object // message ValidateUserLoginRequest in pb/authentication.proto 11 | gql__type_LoginResponse *graphql.Object // message LoginResponse in pb/authentication.proto 12 | gql__type_LoginCallbackResponse *graphql.Object // message LoginCallbackResponse in pb/authentication.proto 13 | gql__type_LoginCallbackRequest *graphql.Object // message LoginCallbackRequest in pb/authentication.proto 14 | gql__input_ValidateUserLoginResponse *graphql.InputObject // message ValidateUserLoginResponse in pb/authentication.proto 15 | gql__input_ValidateUserLoginRequest *graphql.InputObject // message ValidateUserLoginRequest in pb/authentication.proto 16 | gql__input_LoginResponse *graphql.InputObject // message LoginResponse in pb/authentication.proto 17 | gql__input_LoginCallbackResponse *graphql.InputObject // message LoginCallbackResponse in pb/authentication.proto 18 | gql__input_LoginCallbackRequest *graphql.InputObject // message LoginCallbackRequest in pb/authentication.proto 19 | ) 20 | 21 | func Gql__type_ValidateUserLoginResponse() *graphql.Object { 22 | if gql__type_ValidateUserLoginResponse == nil { 23 | gql__type_ValidateUserLoginResponse = graphql.NewObject(graphql.ObjectConfig{ 24 | Name: "Pb_Type_ValidateUserLoginResponse", 25 | Fields: graphql.Fields{ 26 | "id": &graphql.Field{ 27 | Type: graphql.String, 28 | Description: `Id is the user Unique identifier`, 29 | }, 30 | }, 31 | }) 32 | } 33 | return gql__type_ValidateUserLoginResponse 34 | } 35 | 36 | func Gql__type_ValidateUserLoginRequest() *graphql.Object { 37 | if gql__type_ValidateUserLoginRequest == nil { 38 | gql__type_ValidateUserLoginRequest = graphql.NewObject(graphql.ObjectConfig{ 39 | Name: "Pb_Type_ValidateUserLoginRequest", 40 | Description: `ValidateUserLoginRequest contains user login credential.`, 41 | Fields: graphql.Fields{ 42 | "id": &graphql.Field{ 43 | Type: graphql.String, 44 | Description: `Id is the user Unique identifier`, 45 | }, 46 | "password": &graphql.Field{ 47 | Type: graphql.String, 48 | Description: `Password is the user password`, 49 | }, 50 | }, 51 | }) 52 | } 53 | return gql__type_ValidateUserLoginRequest 54 | } 55 | 56 | func Gql__type_LoginResponse() *graphql.Object { 57 | if gql__type_LoginResponse == nil { 58 | gql__type_LoginResponse = graphql.NewObject(graphql.ObjectConfig{ 59 | Name: "Pb_Type_LoginResponse", 60 | Fields: graphql.Fields{ 61 | "url": &graphql.Field{ 62 | Type: graphql.String, 63 | }, 64 | }, 65 | }) 66 | } 67 | return gql__type_LoginResponse 68 | } 69 | 70 | func Gql__type_LoginCallbackResponse() *graphql.Object { 71 | if gql__type_LoginCallbackResponse == nil { 72 | gql__type_LoginCallbackResponse = graphql.NewObject(graphql.ObjectConfig{ 73 | Name: "Pb_Type_LoginCallbackResponse", 74 | Fields: graphql.Fields{ 75 | "id_token": &graphql.Field{ 76 | Type: graphql.String, 77 | }, 78 | "access_token": &graphql.Field{ 79 | Type: graphql.String, 80 | }, 81 | "user_id": &graphql.Field{ 82 | Type: graphql.String, 83 | }, 84 | }, 85 | }) 86 | } 87 | return gql__type_LoginCallbackResponse 88 | } 89 | 90 | func Gql__type_LoginCallbackRequest() *graphql.Object { 91 | if gql__type_LoginCallbackRequest == nil { 92 | gql__type_LoginCallbackRequest = graphql.NewObject(graphql.ObjectConfig{ 93 | Name: "Pb_Type_LoginCallbackRequest", 94 | Fields: graphql.Fields{ 95 | "state": &graphql.Field{ 96 | Type: graphql.String, 97 | }, 98 | "code": &graphql.Field{ 99 | Type: graphql.String, 100 | }, 101 | }, 102 | }) 103 | } 104 | return gql__type_LoginCallbackRequest 105 | } 106 | 107 | func Gql__input_ValidateUserLoginResponse() *graphql.InputObject { 108 | if gql__input_ValidateUserLoginResponse == nil { 109 | gql__input_ValidateUserLoginResponse = graphql.NewInputObject(graphql.InputObjectConfig{ 110 | Name: "Pb_Input_ValidateUserLoginResponse", 111 | Fields: graphql.InputObjectConfigFieldMap{ 112 | "id": &graphql.InputObjectFieldConfig{ 113 | Description: `Id is the user Unique identifier`, 114 | Type: graphql.String, 115 | }, 116 | }, 117 | }) 118 | } 119 | return gql__input_ValidateUserLoginResponse 120 | } 121 | 122 | func Gql__input_ValidateUserLoginRequest() *graphql.InputObject { 123 | if gql__input_ValidateUserLoginRequest == nil { 124 | gql__input_ValidateUserLoginRequest = graphql.NewInputObject(graphql.InputObjectConfig{ 125 | Name: "Pb_Input_ValidateUserLoginRequest", 126 | Fields: graphql.InputObjectConfigFieldMap{ 127 | "id": &graphql.InputObjectFieldConfig{ 128 | Description: `Id is the user Unique identifier`, 129 | Type: graphql.String, 130 | }, 131 | "password": &graphql.InputObjectFieldConfig{ 132 | Description: `Password is the user password`, 133 | Type: graphql.String, 134 | }, 135 | }, 136 | }) 137 | } 138 | return gql__input_ValidateUserLoginRequest 139 | } 140 | 141 | func Gql__input_LoginResponse() *graphql.InputObject { 142 | if gql__input_LoginResponse == nil { 143 | gql__input_LoginResponse = graphql.NewInputObject(graphql.InputObjectConfig{ 144 | Name: "Pb_Input_LoginResponse", 145 | Fields: graphql.InputObjectConfigFieldMap{ 146 | "url": &graphql.InputObjectFieldConfig{ 147 | Type: graphql.String, 148 | }, 149 | }, 150 | }) 151 | } 152 | return gql__input_LoginResponse 153 | } 154 | 155 | func Gql__input_LoginCallbackResponse() *graphql.InputObject { 156 | if gql__input_LoginCallbackResponse == nil { 157 | gql__input_LoginCallbackResponse = graphql.NewInputObject(graphql.InputObjectConfig{ 158 | Name: "Pb_Input_LoginCallbackResponse", 159 | Fields: graphql.InputObjectConfigFieldMap{ 160 | "id_token": &graphql.InputObjectFieldConfig{ 161 | Type: graphql.String, 162 | }, 163 | "access_token": &graphql.InputObjectFieldConfig{ 164 | Type: graphql.String, 165 | }, 166 | "user_id": &graphql.InputObjectFieldConfig{ 167 | Type: graphql.String, 168 | }, 169 | }, 170 | }) 171 | } 172 | return gql__input_LoginCallbackResponse 173 | } 174 | 175 | func Gql__input_LoginCallbackRequest() *graphql.InputObject { 176 | if gql__input_LoginCallbackRequest == nil { 177 | gql__input_LoginCallbackRequest = graphql.NewInputObject(graphql.InputObjectConfig{ 178 | Name: "Pb_Input_LoginCallbackRequest", 179 | Fields: graphql.InputObjectConfigFieldMap{ 180 | "state": &graphql.InputObjectFieldConfig{ 181 | Type: graphql.String, 182 | }, 183 | "code": &graphql.InputObjectFieldConfig{ 184 | Type: graphql.String, 185 | }, 186 | }, 187 | }) 188 | } 189 | return gql__input_LoginCallbackRequest 190 | } 191 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/deqode/GoArcc/logger" 5 | "github.com/spf13/viper" 6 | "go.uber.org/zap" 7 | "os" 8 | "path" 9 | "path/filepath" 10 | "runtime" 11 | "strings" 12 | ) 13 | 14 | type FileInformation struct { 15 | Path string 16 | Name string 17 | } 18 | 19 | //Config : including all the configuration 20 | type Config struct { 21 | Grpc GrpcServerConfig `mapstructure:"GRPC"` 22 | Graphql GraphqlServerConfig `mapstructure:"GRAPHQL"` 23 | Rest RestServerConfig `mapstructure:"REST"` 24 | HealthCheck HealthCheckServerConfig `mapstructure:"HEALTH_CHECK"` 25 | Logger LoggerConfig `mapstructure:"LOGGER"` 26 | Postgres PostgresConfig `mapstructure:"POSTGRES"` 27 | Metrics MetricsConfig `mapstructure:"METRICS"` 28 | Jaeger JaegerServerConfig `mapstructure:"JAEGER"` 29 | Auth AuthConfig `mapstructure:"AUTH"` 30 | GithubVCSConfig VCSSConfig `mapstructure:"GITHUB_VCS_CONFIG"` 31 | CadenceConfig CadenceConfig `mapstructure:"CADENCE_CONFIG"` 32 | SupportedVcsConfig []string 33 | } 34 | 35 | type CadenceConfig struct { 36 | Domain string `mapstructure:"DOMAIN"` 37 | Service string `mapstructure:"SERVICE"` 38 | Port string `mapstructure:"PORT"` 39 | Host string `mapstructure:"HOST"` 40 | } 41 | 42 | // GrpcServerConfig GrpcServerConfig: gRPC server configuration 43 | // Timeout is the request timeout: 44 | // any client request take longer then the given timeout will automatically cancelled. 45 | type GrpcServerConfig struct { 46 | Port string `mapstructure:"PORT"` 47 | Host string `mapstructure:"HOST"` 48 | RequestTimeout int `mapstructure:"REQUEST_TIMEOUT"` 49 | } 50 | 51 | // GraphqlServerConfig GraphqlServerConfig: Graphql server configuration 52 | type GraphqlServerConfig struct { 53 | Port string `mapstructure:"PORT"` 54 | Host string `mapstructure:"HOST"` 55 | RequestTimeout int `mapstructure:"REQUEST_TIMEOUT"` 56 | } 57 | 58 | // RestServerConfig RestServerConfig: Rest Implementation config 59 | type RestServerConfig struct { 60 | Port string `mapstructure:"PORT"` 61 | Host string `mapstructure:"HOST"` 62 | RequestTimeout int `mapstructure:"REQUEST_TIMEOUT"` 63 | } 64 | 65 | // HealthCheckServerConfig HealthCheckServerConfig: Configuration about health check 66 | type HealthCheckServerConfig struct { 67 | Port string `mapstructure:"PORT"` 68 | Host string `mapstructure:"HOST"` 69 | } 70 | 71 | // LoggerConfig LoggerConfig: Zapier log level 72 | type LoggerConfig struct { 73 | LogLevel string `mapstructure:"LOG_LEVEL"` 74 | } 75 | 76 | // PostgresConfig PostgresConfig: detail config about the postgres database 77 | type PostgresConfig struct { 78 | Host string `mapstructure:"HOST"` 79 | Port string `mapstructure:"PORT"` 80 | User string `mapstructure:"USER"` 81 | Password string `mapstructure:"PASSWORD"` 82 | DbName string `mapstructure:"DB_NAME"` 83 | SslMode string `mapstructure:"SSL_MODE"` 84 | Driver string `mapstructure:"PG_DRIVER"` 85 | } 86 | 87 | //MetricsConfig : detail config about the Metrics 88 | type MetricsConfig struct { 89 | URL string `mapstructure:"URL"` 90 | ServiceName string `mapstructure:"SERVICE_NAME"` 91 | } 92 | 93 | //JaegerServerConfig : detail config about the Jaeger 94 | type JaegerServerConfig struct { 95 | Host string `mapstructure:"HOST"` 96 | Port string `mapstructure:"PORT"` 97 | ServiceName string `mapstructure:"SERVICE_NAME"` 98 | LogSpans string `mapstructure:"LOG_SPANS"` 99 | } 100 | 101 | // AuthConfig Authentication config: details provided by Auth0 102 | type AuthConfig struct { 103 | Auth0ClientID string `mapstructure:"AUTH0_CLIENT_ID"` 104 | Auth0Domain string `mapstructure:"AUTH0_DOMAIN"` 105 | Auth0ClientSecret string `mapstructure:"AUTH0_CLIENT_SECRET"` 106 | Auth0CallBackURL string `mapstructure:"AUTH0_CALL_BACK_URL"` 107 | } 108 | 109 | //VCSSConfig like github,gitlab,bitbucket config 110 | type VCSSConfig struct { 111 | IType int `mapstructure:"ITYPE"` 112 | Provider string `mapstructure:"PROVIDER"` 113 | URLTemplate string `mapstructure:"URL_TEMPLATE"` 114 | ClientID string `mapstructure:"CLIENT_ID"` 115 | RedirectURI string `mapstructure:"REDIRECT_URI"` 116 | State string `mapstructure:"STATE"` 117 | Scope string `mapstructure:"SCOPE"` 118 | ResponseType string `mapstructure:"RESPONSE_TYPE"` 119 | ClientSecret string `mapstructure:"CLIENT_SECRET"` 120 | Name string `mapstructure:"NAME"` 121 | } 122 | 123 | //GetVcsConfig : will give the particular vcs config 124 | func GetVcsConfig(name string, vcsConfig []VCSSConfig) *VCSSConfig { 125 | for _, v := range vcsConfig { 126 | if v.Name == name { 127 | return &v 128 | } 129 | } 130 | return nil 131 | } 132 | 133 | // LoadConfig config file from given path 134 | func LoadConfig(filename, path string) (*viper.Viper, error) { 135 | v := viper.New() 136 | v.AddConfigPath(path) 137 | v.SetConfigName(filename) 138 | v.AutomaticEnv() 139 | v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 140 | if err := v.ReadInConfig(); err != nil { 141 | return nil, err 142 | } 143 | 144 | return v, nil 145 | } 146 | 147 | // ParseConfig file from the given viper 148 | func ParseConfig(v *viper.Viper) (*Config, error) { 149 | var c Config 150 | err := v.Unmarshal(&c) 151 | if err != nil { 152 | logger.Log.Fatal("unable to decode into struct", zap.Error(err)) 153 | return nil, err 154 | } 155 | return &c, nil 156 | } 157 | 158 | // GetConfigName get the path from local or docker 159 | func GetConfigName() string { 160 | fileName := os.Getenv("CONFIG_NAME") 161 | if fileName != "" { 162 | return fileName 163 | } 164 | return "config" 165 | } 166 | 167 | func GetConfigDirectory() string { 168 | filePath := os.Getenv("CONFIG_DIRECTORY") 169 | if filePath != "" { 170 | return filePath 171 | } 172 | return RootDir() 173 | } 174 | func RootDir() string { 175 | _, b, _, _ := runtime.Caller(0) 176 | d := path.Join(path.Dir(b)) 177 | return filepath.Dir(d) 178 | } 179 | 180 | // GetConfig : will get the config 181 | func GetConfig() *Config { 182 | configFileName := GetConfigName() 183 | configFileDirectory := GetConfigDirectory() 184 | logger.Log.Info("Config Details", zap.String("configFileDirectory", configFileDirectory), zap.String("configFileName", configFileName)) 185 | 186 | cfgFile, configFileLoadError := LoadConfig(configFileName, configFileDirectory) 187 | if configFileLoadError != nil { 188 | logger.Log.Fatal("unable to get config", zap.Error(configFileLoadError)) 189 | panic(configFileLoadError) 190 | } 191 | 192 | cfg, parseError := ParseConfig(cfgFile) 193 | if parseError != nil { 194 | logger.Log.Fatal("unable to get config", zap.Error(parseError)) 195 | panic(parseError) 196 | } 197 | 198 | cfg.SupportedVcsConfig = supportedVcsConfig() 199 | return cfg 200 | } 201 | 202 | // SupportedVcsConfig add supported type from here. 203 | func supportedVcsConfig() []string { 204 | return []string{"github"} 205 | } 206 | -------------------------------------------------------------------------------- /modules/user-profile/v1/pb/user_profile_int_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | 3 | package pb 4 | 5 | import ( 6 | context "context" 7 | empty "github.com/golang/protobuf/ptypes/empty" 8 | grpc "google.golang.org/grpc" 9 | codes "google.golang.org/grpc/codes" 10 | status "google.golang.org/grpc/status" 11 | ) 12 | 13 | // This is a compile-time assertion to ensure that this generated file 14 | // is compatible with the grpc package it is being compiled against. 15 | const _ = grpc.SupportPackageIsVersion7 16 | 17 | // UserProfileInternalClient is the client API for UserProfileInternal service. 18 | // 19 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 20 | type UserProfileInternalClient interface { 21 | //CreateUserProfile creates a user profile by external oAuth 22 | CreateUserProfile(ctx context.Context, in *CreateUserProfileRequest, opts ...grpc.CallOption) (*UserProfile, error) 23 | // CreateUserProfile will update userprofile 24 | UpdateUserProfile(ctx context.Context, in *UpdateUserProfileRequest, opts ...grpc.CallOption) (*UserProfile, error) 25 | // DeleteUserProfile delete the user 26 | DeleteUserProfile(ctx context.Context, in *DeleteUserProfileRequest, opts ...grpc.CallOption) (*empty.Empty, error) 27 | } 28 | 29 | type userProfileInternalClient struct { 30 | cc grpc.ClientConnInterface 31 | } 32 | 33 | func NewUserProfileInternalClient(cc grpc.ClientConnInterface) UserProfileInternalClient { 34 | return &userProfileInternalClient{cc} 35 | } 36 | 37 | func (c *userProfileInternalClient) CreateUserProfile(ctx context.Context, in *CreateUserProfileRequest, opts ...grpc.CallOption) (*UserProfile, error) { 38 | out := new(UserProfile) 39 | err := c.cc.Invoke(ctx, "/goarcc.user_profile_internal.v1.UserProfileInternal/CreateUserProfile", in, out, opts...) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return out, nil 44 | } 45 | 46 | func (c *userProfileInternalClient) UpdateUserProfile(ctx context.Context, in *UpdateUserProfileRequest, opts ...grpc.CallOption) (*UserProfile, error) { 47 | out := new(UserProfile) 48 | err := c.cc.Invoke(ctx, "/goarcc.user_profile_internal.v1.UserProfileInternal/UpdateUserProfile", in, out, opts...) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return out, nil 53 | } 54 | 55 | func (c *userProfileInternalClient) DeleteUserProfile(ctx context.Context, in *DeleteUserProfileRequest, opts ...grpc.CallOption) (*empty.Empty, error) { 56 | out := new(empty.Empty) 57 | err := c.cc.Invoke(ctx, "/goarcc.user_profile_internal.v1.UserProfileInternal/DeleteUserProfile", in, out, opts...) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return out, nil 62 | } 63 | 64 | // UserProfileInternalServer is the server API for UserProfileInternal service. 65 | // All implementations should embed UnimplementedUserProfileInternalServer 66 | // for forward compatibility 67 | type UserProfileInternalServer interface { 68 | //CreateUserProfile creates a user profile by external oAuth 69 | CreateUserProfile(context.Context, *CreateUserProfileRequest) (*UserProfile, error) 70 | // CreateUserProfile will update userprofile 71 | UpdateUserProfile(context.Context, *UpdateUserProfileRequest) (*UserProfile, error) 72 | // DeleteUserProfile delete the user 73 | DeleteUserProfile(context.Context, *DeleteUserProfileRequest) (*empty.Empty, error) 74 | } 75 | 76 | // UnimplementedUserProfileInternalServer should be embedded to have forward compatible implementations. 77 | type UnimplementedUserProfileInternalServer struct { 78 | } 79 | 80 | func (UnimplementedUserProfileInternalServer) CreateUserProfile(context.Context, *CreateUserProfileRequest) (*UserProfile, error) { 81 | return nil, status.Errorf(codes.Unimplemented, "method CreateUserProfile not implemented") 82 | } 83 | func (UnimplementedUserProfileInternalServer) UpdateUserProfile(context.Context, *UpdateUserProfileRequest) (*UserProfile, error) { 84 | return nil, status.Errorf(codes.Unimplemented, "method UpdateUserProfile not implemented") 85 | } 86 | func (UnimplementedUserProfileInternalServer) DeleteUserProfile(context.Context, *DeleteUserProfileRequest) (*empty.Empty, error) { 87 | return nil, status.Errorf(codes.Unimplemented, "method DeleteUserProfile not implemented") 88 | } 89 | 90 | // UnsafeUserProfileInternalServer may be embedded to opt out of forward compatibility for this service. 91 | // Use of this interface is not recommended, as added methods to UserProfileInternalServer will 92 | // result in compilation errors. 93 | type UnsafeUserProfileInternalServer interface { 94 | mustEmbedUnimplementedUserProfileInternalServer() 95 | } 96 | 97 | func RegisterUserProfileInternalServer(s *grpc.Server, srv UserProfileInternalServer) { 98 | s.RegisterService(&_UserProfileInternal_serviceDesc, srv) 99 | } 100 | 101 | func _UserProfileInternal_CreateUserProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 102 | in := new(CreateUserProfileRequest) 103 | if err := dec(in); err != nil { 104 | return nil, err 105 | } 106 | if interceptor == nil { 107 | return srv.(UserProfileInternalServer).CreateUserProfile(ctx, in) 108 | } 109 | info := &grpc.UnaryServerInfo{ 110 | Server: srv, 111 | FullMethod: "/goarcc.user_profile_internal.v1.UserProfileInternal/CreateUserProfile", 112 | } 113 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 114 | return srv.(UserProfileInternalServer).CreateUserProfile(ctx, req.(*CreateUserProfileRequest)) 115 | } 116 | return interceptor(ctx, in, info, handler) 117 | } 118 | 119 | func _UserProfileInternal_UpdateUserProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 120 | in := new(UpdateUserProfileRequest) 121 | if err := dec(in); err != nil { 122 | return nil, err 123 | } 124 | if interceptor == nil { 125 | return srv.(UserProfileInternalServer).UpdateUserProfile(ctx, in) 126 | } 127 | info := &grpc.UnaryServerInfo{ 128 | Server: srv, 129 | FullMethod: "/goarcc.user_profile_internal.v1.UserProfileInternal/UpdateUserProfile", 130 | } 131 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 132 | return srv.(UserProfileInternalServer).UpdateUserProfile(ctx, req.(*UpdateUserProfileRequest)) 133 | } 134 | return interceptor(ctx, in, info, handler) 135 | } 136 | 137 | func _UserProfileInternal_DeleteUserProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 138 | in := new(DeleteUserProfileRequest) 139 | if err := dec(in); err != nil { 140 | return nil, err 141 | } 142 | if interceptor == nil { 143 | return srv.(UserProfileInternalServer).DeleteUserProfile(ctx, in) 144 | } 145 | info := &grpc.UnaryServerInfo{ 146 | Server: srv, 147 | FullMethod: "/goarcc.user_profile_internal.v1.UserProfileInternal/DeleteUserProfile", 148 | } 149 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 150 | return srv.(UserProfileInternalServer).DeleteUserProfile(ctx, req.(*DeleteUserProfileRequest)) 151 | } 152 | return interceptor(ctx, in, info, handler) 153 | } 154 | 155 | var _UserProfileInternal_serviceDesc = grpc.ServiceDesc{ 156 | ServiceName: "goarcc.user_profile_internal.v1.UserProfileInternal", 157 | HandlerType: (*UserProfileInternalServer)(nil), 158 | Methods: []grpc.MethodDesc{ 159 | { 160 | MethodName: "CreateUserProfile", 161 | Handler: _UserProfileInternal_CreateUserProfile_Handler, 162 | }, 163 | { 164 | MethodName: "UpdateUserProfile", 165 | Handler: _UserProfileInternal_UpdateUserProfile_Handler, 166 | }, 167 | { 168 | MethodName: "DeleteUserProfile", 169 | Handler: _UserProfileInternal_DeleteUserProfile_Handler, 170 | }, 171 | }, 172 | Streams: []grpc.StreamDesc{}, 173 | Metadata: "pb/user_profile_int.proto", 174 | } 175 | -------------------------------------------------------------------------------- /modules/user-profile/v1/pb/user_profile_int.pb.validate.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-validate. DO NOT EDIT. 2 | // source: pb/user_profile_int.proto 3 | 4 | package pb 5 | 6 | import ( 7 | "bytes" 8 | "errors" 9 | "fmt" 10 | "net" 11 | "net/mail" 12 | "net/url" 13 | "regexp" 14 | "strings" 15 | "time" 16 | "unicode/utf8" 17 | 18 | "google.golang.org/protobuf/types/known/anypb" 19 | ) 20 | 21 | // ensure the imports are used 22 | var ( 23 | _ = bytes.MinRead 24 | _ = errors.New("") 25 | _ = fmt.Print 26 | _ = utf8.UTFMax 27 | _ = (*regexp.Regexp)(nil) 28 | _ = (*strings.Reader)(nil) 29 | _ = net.IPv4len 30 | _ = time.Duration(0) 31 | _ = (*url.URL)(nil) 32 | _ = (*mail.Address)(nil) 33 | _ = anypb.Any{} 34 | ) 35 | 36 | // Validate checks the field values on CreateUserProfileRequest with the rules 37 | // defined in the proto definition for this message. If any rules are 38 | // violated, an error is returned. 39 | func (m *CreateUserProfileRequest) Validate() error { 40 | if m == nil { 41 | return nil 42 | } 43 | 44 | if v, ok := interface{}(m.GetUserProfile()).(interface{ Validate() error }); ok { 45 | if err := v.Validate(); err != nil { 46 | return CreateUserProfileRequestValidationError{ 47 | field: "UserProfile", 48 | reason: "embedded message failed validation", 49 | cause: err, 50 | } 51 | } 52 | } 53 | 54 | return nil 55 | } 56 | 57 | // CreateUserProfileRequestValidationError is the validation error returned by 58 | // CreateUserProfileRequest.Validate if the designated constraints aren't met. 59 | type CreateUserProfileRequestValidationError struct { 60 | field string 61 | reason string 62 | cause error 63 | key bool 64 | } 65 | 66 | // Field function returns field value. 67 | func (e CreateUserProfileRequestValidationError) Field() string { return e.field } 68 | 69 | // Reason function returns reason value. 70 | func (e CreateUserProfileRequestValidationError) Reason() string { return e.reason } 71 | 72 | // Cause function returns cause value. 73 | func (e CreateUserProfileRequestValidationError) Cause() error { return e.cause } 74 | 75 | // Key function returns key value. 76 | func (e CreateUserProfileRequestValidationError) Key() bool { return e.key } 77 | 78 | // ErrorName returns error name. 79 | func (e CreateUserProfileRequestValidationError) ErrorName() string { 80 | return "CreateUserProfileRequestValidationError" 81 | } 82 | 83 | // Error satisfies the builtin error interface 84 | func (e CreateUserProfileRequestValidationError) Error() string { 85 | cause := "" 86 | if e.cause != nil { 87 | cause = fmt.Sprintf(" | caused by: %v", e.cause) 88 | } 89 | 90 | key := "" 91 | if e.key { 92 | key = "key for " 93 | } 94 | 95 | return fmt.Sprintf( 96 | "invalid %sCreateUserProfileRequest.%s: %s%s", 97 | key, 98 | e.field, 99 | e.reason, 100 | cause) 101 | } 102 | 103 | var _ error = CreateUserProfileRequestValidationError{} 104 | 105 | var _ interface { 106 | Field() string 107 | Reason() string 108 | Key() bool 109 | Cause() error 110 | ErrorName() string 111 | } = CreateUserProfileRequestValidationError{} 112 | 113 | // Validate checks the field values on DeleteUserProfileRequest with the rules 114 | // defined in the proto definition for this message. If any rules are 115 | // violated, an error is returned. 116 | func (m *DeleteUserProfileRequest) Validate() error { 117 | if m == nil { 118 | return nil 119 | } 120 | 121 | if utf8.RuneCountInString(m.GetId()) < 3 { 122 | return DeleteUserProfileRequestValidationError{ 123 | field: "Id", 124 | reason: "value length must be at least 3 runes", 125 | } 126 | } 127 | 128 | return nil 129 | } 130 | 131 | // DeleteUserProfileRequestValidationError is the validation error returned by 132 | // DeleteUserProfileRequest.Validate if the designated constraints aren't met. 133 | type DeleteUserProfileRequestValidationError struct { 134 | field string 135 | reason string 136 | cause error 137 | key bool 138 | } 139 | 140 | // Field function returns field value. 141 | func (e DeleteUserProfileRequestValidationError) Field() string { return e.field } 142 | 143 | // Reason function returns reason value. 144 | func (e DeleteUserProfileRequestValidationError) Reason() string { return e.reason } 145 | 146 | // Cause function returns cause value. 147 | func (e DeleteUserProfileRequestValidationError) Cause() error { return e.cause } 148 | 149 | // Key function returns key value. 150 | func (e DeleteUserProfileRequestValidationError) Key() bool { return e.key } 151 | 152 | // ErrorName returns error name. 153 | func (e DeleteUserProfileRequestValidationError) ErrorName() string { 154 | return "DeleteUserProfileRequestValidationError" 155 | } 156 | 157 | // Error satisfies the builtin error interface 158 | func (e DeleteUserProfileRequestValidationError) Error() string { 159 | cause := "" 160 | if e.cause != nil { 161 | cause = fmt.Sprintf(" | caused by: %v", e.cause) 162 | } 163 | 164 | key := "" 165 | if e.key { 166 | key = "key for " 167 | } 168 | 169 | return fmt.Sprintf( 170 | "invalid %sDeleteUserProfileRequest.%s: %s%s", 171 | key, 172 | e.field, 173 | e.reason, 174 | cause) 175 | } 176 | 177 | var _ error = DeleteUserProfileRequestValidationError{} 178 | 179 | var _ interface { 180 | Field() string 181 | Reason() string 182 | Key() bool 183 | Cause() error 184 | ErrorName() string 185 | } = DeleteUserProfileRequestValidationError{} 186 | 187 | // Validate checks the field values on UpdateUserProfileRequest with the rules 188 | // defined in the proto definition for this message. If any rules are 189 | // violated, an error is returned. 190 | func (m *UpdateUserProfileRequest) Validate() error { 191 | if m == nil { 192 | return nil 193 | } 194 | 195 | if m.GetUserProfile() == nil { 196 | return UpdateUserProfileRequestValidationError{ 197 | field: "UserProfile", 198 | reason: "value is required", 199 | } 200 | } 201 | 202 | if v, ok := interface{}(m.GetUserProfile()).(interface{ Validate() error }); ok { 203 | if err := v.Validate(); err != nil { 204 | return UpdateUserProfileRequestValidationError{ 205 | field: "UserProfile", 206 | reason: "embedded message failed validation", 207 | cause: err, 208 | } 209 | } 210 | } 211 | 212 | return nil 213 | } 214 | 215 | // UpdateUserProfileRequestValidationError is the validation error returned by 216 | // UpdateUserProfileRequest.Validate if the designated constraints aren't met. 217 | type UpdateUserProfileRequestValidationError struct { 218 | field string 219 | reason string 220 | cause error 221 | key bool 222 | } 223 | 224 | // Field function returns field value. 225 | func (e UpdateUserProfileRequestValidationError) Field() string { return e.field } 226 | 227 | // Reason function returns reason value. 228 | func (e UpdateUserProfileRequestValidationError) Reason() string { return e.reason } 229 | 230 | // Cause function returns cause value. 231 | func (e UpdateUserProfileRequestValidationError) Cause() error { return e.cause } 232 | 233 | // Key function returns key value. 234 | func (e UpdateUserProfileRequestValidationError) Key() bool { return e.key } 235 | 236 | // ErrorName returns error name. 237 | func (e UpdateUserProfileRequestValidationError) ErrorName() string { 238 | return "UpdateUserProfileRequestValidationError" 239 | } 240 | 241 | // Error satisfies the builtin error interface 242 | func (e UpdateUserProfileRequestValidationError) Error() string { 243 | cause := "" 244 | if e.cause != nil { 245 | cause = fmt.Sprintf(" | caused by: %v", e.cause) 246 | } 247 | 248 | key := "" 249 | if e.key { 250 | key = "key for " 251 | } 252 | 253 | return fmt.Sprintf( 254 | "invalid %sUpdateUserProfileRequest.%s: %s%s", 255 | key, 256 | e.field, 257 | e.reason, 258 | cause) 259 | } 260 | 261 | var _ error = UpdateUserProfileRequestValidationError{} 262 | 263 | var _ interface { 264 | Field() string 265 | Reason() string 266 | Key() bool 267 | Cause() error 268 | ErrorName() string 269 | } = UpdateUserProfileRequestValidationError{} 270 | -------------------------------------------------------------------------------- /modules/user-profile/v1/pb/user_profile.pb.validate.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-validate. DO NOT EDIT. 2 | // source: pb/user_profile.proto 3 | 4 | package pb 5 | 6 | import ( 7 | "bytes" 8 | "errors" 9 | "fmt" 10 | "net" 11 | "net/mail" 12 | "net/url" 13 | "regexp" 14 | "strings" 15 | "time" 16 | "unicode/utf8" 17 | 18 | "google.golang.org/protobuf/types/known/anypb" 19 | 20 | types "github.com/deqode/GoArcc/protos/types" 21 | ) 22 | 23 | // ensure the imports are used 24 | var ( 25 | _ = bytes.MinRead 26 | _ = errors.New("") 27 | _ = fmt.Print 28 | _ = utf8.UTFMax 29 | _ = (*regexp.Regexp)(nil) 30 | _ = (*strings.Reader)(nil) 31 | _ = net.IPv4len 32 | _ = time.Duration(0) 33 | _ = (*url.URL)(nil) 34 | _ = (*mail.Address)(nil) 35 | _ = anypb.Any{} 36 | 37 | _ = types.VCSProviders(0) 38 | ) 39 | 40 | // Validate checks the field values on GetUserProfileBySubRequest with the 41 | // rules defined in the proto definition for this message. If any rules are 42 | // violated, an error is returned. 43 | func (m *GetUserProfileBySubRequest) Validate() error { 44 | if m == nil { 45 | return nil 46 | } 47 | 48 | if utf8.RuneCountInString(m.GetSub()) < 3 { 49 | return GetUserProfileBySubRequestValidationError{ 50 | field: "Sub", 51 | reason: "value length must be at least 3 runes", 52 | } 53 | } 54 | 55 | return nil 56 | } 57 | 58 | // GetUserProfileBySubRequestValidationError is the validation error returned 59 | // by GetUserProfileBySubRequest.Validate if the designated constraints aren't met. 60 | type GetUserProfileBySubRequestValidationError struct { 61 | field string 62 | reason string 63 | cause error 64 | key bool 65 | } 66 | 67 | // Field function returns field value. 68 | func (e GetUserProfileBySubRequestValidationError) Field() string { return e.field } 69 | 70 | // Reason function returns reason value. 71 | func (e GetUserProfileBySubRequestValidationError) Reason() string { return e.reason } 72 | 73 | // Cause function returns cause value. 74 | func (e GetUserProfileBySubRequestValidationError) Cause() error { return e.cause } 75 | 76 | // Key function returns key value. 77 | func (e GetUserProfileBySubRequestValidationError) Key() bool { return e.key } 78 | 79 | // ErrorName returns error name. 80 | func (e GetUserProfileBySubRequestValidationError) ErrorName() string { 81 | return "GetUserProfileBySubRequestValidationError" 82 | } 83 | 84 | // Error satisfies the builtin error interface 85 | func (e GetUserProfileBySubRequestValidationError) Error() string { 86 | cause := "" 87 | if e.cause != nil { 88 | cause = fmt.Sprintf(" | caused by: %v", e.cause) 89 | } 90 | 91 | key := "" 92 | if e.key { 93 | key = "key for " 94 | } 95 | 96 | return fmt.Sprintf( 97 | "invalid %sGetUserProfileBySubRequest.%s: %s%s", 98 | key, 99 | e.field, 100 | e.reason, 101 | cause) 102 | } 103 | 104 | var _ error = GetUserProfileBySubRequestValidationError{} 105 | 106 | var _ interface { 107 | Field() string 108 | Reason() string 109 | Key() bool 110 | Cause() error 111 | ErrorName() string 112 | } = GetUserProfileBySubRequestValidationError{} 113 | 114 | // Validate checks the field values on UserProfile with the rules defined in 115 | // the proto definition for this message. If any rules are violated, an error 116 | // is returned. 117 | func (m *UserProfile) Validate() error { 118 | if m == nil { 119 | return nil 120 | } 121 | 122 | // no validation rules for Id 123 | 124 | if l := utf8.RuneCountInString(m.GetSub()); l < 3 || l > 100 { 125 | return UserProfileValidationError{ 126 | field: "Sub", 127 | reason: "value length must be between 3 and 100 runes, inclusive", 128 | } 129 | } 130 | 131 | if l := utf8.RuneCountInString(m.GetName()); l < 1 || l > 100 { 132 | return UserProfileValidationError{ 133 | field: "Name", 134 | reason: "value length must be between 1 and 100 runes, inclusive", 135 | } 136 | } 137 | 138 | // no validation rules for UserName 139 | 140 | if utf8.RuneCountInString(m.GetEmail()) < 0 { 141 | return UserProfileValidationError{ 142 | field: "Email", 143 | reason: "value length must be at least 0 runes", 144 | } 145 | } 146 | 147 | if err := m._validateEmail(m.GetEmail()); err != nil { 148 | return UserProfileValidationError{ 149 | field: "Email", 150 | reason: "value must be a valid email address", 151 | cause: err, 152 | } 153 | } 154 | 155 | // no validation rules for PhoneNumber 156 | 157 | if _, ok := _UserProfile_ExternalSource_NotInLookup[m.GetExternalSource()]; ok { 158 | return UserProfileValidationError{ 159 | field: "ExternalSource", 160 | reason: "value must not be in list [0]", 161 | } 162 | } 163 | 164 | // no validation rules for ProfilePicUrl 165 | 166 | if v, ok := interface{}(m.GetTokenValidTill()).(interface{ Validate() error }); ok { 167 | if err := v.Validate(); err != nil { 168 | return UserProfileValidationError{ 169 | field: "TokenValidTill", 170 | reason: "embedded message failed validation", 171 | cause: err, 172 | } 173 | } 174 | } 175 | 176 | return nil 177 | } 178 | 179 | func (m *UserProfile) _validateHostname(host string) error { 180 | s := strings.ToLower(strings.TrimSuffix(host, ".")) 181 | 182 | if len(host) > 253 { 183 | return errors.New("hostname cannot exceed 253 characters") 184 | } 185 | 186 | for _, part := range strings.Split(s, ".") { 187 | if l := len(part); l == 0 || l > 63 { 188 | return errors.New("hostname part must be non-empty and cannot exceed 63 characters") 189 | } 190 | 191 | if part[0] == '-' { 192 | return errors.New("hostname parts cannot begin with hyphens") 193 | } 194 | 195 | if part[len(part)-1] == '-' { 196 | return errors.New("hostname parts cannot end with hyphens") 197 | } 198 | 199 | for _, r := range part { 200 | if (r < 'a' || r > 'z') && (r < '0' || r > '9') && r != '-' { 201 | return fmt.Errorf("hostname parts can only contain alphanumeric characters or hyphens, got %q", string(r)) 202 | } 203 | } 204 | } 205 | 206 | return nil 207 | } 208 | 209 | func (m *UserProfile) _validateEmail(addr string) error { 210 | a, err := mail.ParseAddress(addr) 211 | if err != nil { 212 | return err 213 | } 214 | addr = a.Address 215 | 216 | if len(addr) > 254 { 217 | return errors.New("email addresses cannot exceed 254 characters") 218 | } 219 | 220 | parts := strings.SplitN(addr, "@", 2) 221 | 222 | if len(parts[0]) > 64 { 223 | return errors.New("email address local phrase cannot exceed 64 characters") 224 | } 225 | 226 | return m._validateHostname(parts[1]) 227 | } 228 | 229 | // UserProfileValidationError is the validation error returned by 230 | // UserProfile.Validate if the designated constraints aren't met. 231 | type UserProfileValidationError struct { 232 | field string 233 | reason string 234 | cause error 235 | key bool 236 | } 237 | 238 | // Field function returns field value. 239 | func (e UserProfileValidationError) Field() string { return e.field } 240 | 241 | // Reason function returns reason value. 242 | func (e UserProfileValidationError) Reason() string { return e.reason } 243 | 244 | // Cause function returns cause value. 245 | func (e UserProfileValidationError) Cause() error { return e.cause } 246 | 247 | // Key function returns key value. 248 | func (e UserProfileValidationError) Key() bool { return e.key } 249 | 250 | // ErrorName returns error name. 251 | func (e UserProfileValidationError) ErrorName() string { return "UserProfileValidationError" } 252 | 253 | // Error satisfies the builtin error interface 254 | func (e UserProfileValidationError) Error() string { 255 | cause := "" 256 | if e.cause != nil { 257 | cause = fmt.Sprintf(" | caused by: %v", e.cause) 258 | } 259 | 260 | key := "" 261 | if e.key { 262 | key = "key for " 263 | } 264 | 265 | return fmt.Sprintf( 266 | "invalid %sUserProfile.%s: %s%s", 267 | key, 268 | e.field, 269 | e.reason, 270 | cause) 271 | } 272 | 273 | var _ error = UserProfileValidationError{} 274 | 275 | var _ interface { 276 | Field() string 277 | Reason() string 278 | Key() bool 279 | Cause() error 280 | ErrorName() string 281 | } = UserProfileValidationError{} 282 | 283 | var _UserProfile_ExternalSource_NotInLookup = map[types.VCSProviders]struct{}{ 284 | 0: {}, 285 | } 286 | -------------------------------------------------------------------------------- /protos/google/api/field_mask.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package google.protobuf; 34 | 35 | option csharp_namespace = "Google.Protobuf.WellKnownTypes"; 36 | option java_package = "com.google.protobuf"; 37 | option java_outer_classname = "FieldMaskProto"; 38 | option java_multiple_files = true; 39 | option objc_class_prefix = "GPB"; 40 | option go_package = "google.golang.org/protobuf/types/known/fieldmaskpb"; 41 | option cc_enable_arenas = true; 42 | 43 | // `FieldMask` represents a set of symbolic field paths, for example: 44 | // 45 | // paths: "f.a" 46 | // paths: "f.b.d" 47 | // 48 | // Here `f` represents a field in some root message, `a` and `b` 49 | // fields in the message found in `f`, and `d` a field found in the 50 | // message in `f.b`. 51 | // 52 | // Field masks are used to specify a subset of fields that should be 53 | // returned by a get operation or modified by an update operation. 54 | // Field masks also have a custom JSON encoding (see below). 55 | // 56 | // # Field Masks in Projections 57 | // 58 | // When used in the context of a projection, a response message or 59 | // sub-message is filtered by the API to only contain those fields as 60 | // specified in the mask. For example, if the mask in the previous 61 | // example is applied to a response message as follows: 62 | // 63 | // f { 64 | // a : 22 65 | // b { 66 | // d : 1 67 | // x : 2 68 | // } 69 | // y : 13 70 | // } 71 | // z: 8 72 | // 73 | // The result will not contain specific values for fields x,y and z 74 | // (their value will be set to the default, and omitted in proto text 75 | // output): 76 | // 77 | // 78 | // f { 79 | // a : 22 80 | // b { 81 | // d : 1 82 | // } 83 | // } 84 | // 85 | // A repeated field is not allowed except at the last position of a 86 | // paths string. 87 | // 88 | // If a FieldMask object is not present in a get operation, the 89 | // operation applies to all fields (as if a FieldMask of all fields 90 | // had been specified). 91 | // 92 | // Note that a field mask does not necessarily apply to the 93 | // top-level response message. In case of a REST get operation, the 94 | // field mask applies directly to the response, but in case of a REST 95 | // list operation, the mask instead applies to each individual message 96 | // in the returned resource list. In case of a REST custom method, 97 | // other definitions may be used. Where the mask applies will be 98 | // clearly documented together with its declaration in the API. In 99 | // any case, the effect on the returned resource/resources is required 100 | // behavior for APIs. 101 | // 102 | // # Field Masks in Update Operations 103 | // 104 | // A field mask in update operations specifies which fields of the 105 | // targeted resource are going to be updated. The API is required 106 | // to only change the values of the fields as specified in the mask 107 | // and leave the others untouched. If a resource is passed in to 108 | // describe the updated values, the API ignores the values of all 109 | // fields not covered by the mask. 110 | // 111 | // If a repeated field is specified for an update operation, new values will 112 | // be appended to the existing repeated field in the target resource. Note that 113 | // a repeated field is only allowed in the last position of a `paths` string. 114 | // 115 | // If a sub-message is specified in the last position of the field mask for an 116 | // update operation, then new value will be merged into the existing sub-message 117 | // in the target resource. 118 | // 119 | // For example, given the target message: 120 | // 121 | // f { 122 | // b { 123 | // d: 1 124 | // x: 2 125 | // } 126 | // c: [1] 127 | // } 128 | // 129 | // And an update message: 130 | // 131 | // f { 132 | // b { 133 | // d: 10 134 | // } 135 | // c: [2] 136 | // } 137 | // 138 | // then if the field mask is: 139 | // 140 | // paths: ["f.b", "f.c"] 141 | // 142 | // then the result will be: 143 | // 144 | // f { 145 | // b { 146 | // d: 10 147 | // x: 2 148 | // } 149 | // c: [1, 2] 150 | // } 151 | // 152 | // An implementation may provide options to override this default behavior for 153 | // repeated and message fields. 154 | // 155 | // In order to reset a field's value to the default, the field must 156 | // be in the mask and set to the default value in the provided resource. 157 | // Hence, in order to reset all fields of a resource, provide a default 158 | // instance of the resource and set all fields in the mask, or do 159 | // not provide a mask as described below. 160 | // 161 | // If a field mask is not present on update, the operation applies to 162 | // all fields (as if a field mask of all fields has been specified). 163 | // Note that in the presence of schema evolution, this may mean that 164 | // fields the client does not know and has therefore not filled into 165 | // the request will be reset to their default. If this is unwanted 166 | // behavior, a specific service may require a client to always specify 167 | // a field mask, producing an error if not. 168 | // 169 | // As with get operations, the location of the resource which 170 | // describes the updated values in the request message depends on the 171 | // operation kind. In any case, the effect of the field mask is 172 | // required to be honored by the API. 173 | // 174 | // ## Considerations for HTTP REST 175 | // 176 | // The HTTP kind of an update operation which uses a field mask must 177 | // be set to PATCH instead of PUT in order to satisfy HTTP semantics 178 | // (PUT must only be used for full updates). 179 | // 180 | // # JSON Encoding of Field Masks 181 | // 182 | // In JSON, a field mask is encoded as a single string where paths are 183 | // separated by a comma. Fields name in each path are converted 184 | // to/from lower-camel naming conventions. 185 | // 186 | // As an example, consider the following message declarations: 187 | // 188 | // message Profile { 189 | // User user = 1; 190 | // Photo photo = 2; 191 | // } 192 | // message User { 193 | // string display_name = 1; 194 | // string address = 2; 195 | // } 196 | // 197 | // In proto a field mask for `Profile` may look as such: 198 | // 199 | // mask { 200 | // paths: "user.display_name" 201 | // paths: "photo" 202 | // } 203 | // 204 | // In JSON, the same mask is represented as below: 205 | // 206 | // { 207 | // mask: "user.displayName,photo" 208 | // } 209 | // 210 | // # Field Masks and Oneof Fields 211 | // 212 | // Field masks treat fields in oneofs just as regular fields. Consider the 213 | // following message: 214 | // 215 | // message SampleMessage { 216 | // oneof test_oneof { 217 | // string name = 4; 218 | // SubMessage sub_message = 9; 219 | // } 220 | // } 221 | // 222 | // The field mask can be: 223 | // 224 | // mask { 225 | // paths: "name" 226 | // } 227 | // 228 | // Or: 229 | // 230 | // mask { 231 | // paths: "sub_message" 232 | // } 233 | // 234 | // Note that oneof type names ("test_oneof" in this case) cannot be used in 235 | // paths. 236 | // 237 | // ## Field Mask Verification 238 | // 239 | // The implementation of any API method which has a FieldMask type field in the 240 | // request should verify the included field paths, and return an 241 | // `INVALID_ARGUMENT` error if any path is unmappable. 242 | message FieldMask { 243 | // The set of field mask paths. 244 | repeated string paths = 1; 245 | } 246 | --------------------------------------------------------------------------------