From a63d66100aed755aff86dfd2ba3e0e6a0a2bae96 Mon Sep 17 00:00:00 2001 From: Varnit Goyal Date: Wed, 21 Aug 2024 13:01:17 +0530 Subject: [PATCH 1/2] TP-55555 | cybertron ui (#13) * TP-55555 | cybertron ui * TP-55555 clean up * TP-55555 clean up * TP-55555 | latest push --- .air.toml | 51 ++++++++++++++ .gitignore | 2 + configs/Elastic.go | 19 ++++++ configs/application.yml | 10 ++- configs/config.go | 6 ++ go.mod | 31 ++++++--- internal/client/elastic/elastic.go | 97 +++++++++++++++++++++++++++ internal/dependencies/dependencies.go | 16 +++-- internal/transport/handler/search.go | 27 ++++++++ internal/transport/router/search.go | 17 +++++ internal/transport/server.go | 10 +++ service/searchService.go | 49 ++++++++++++++ 12 files changed, 319 insertions(+), 16 deletions(-) create mode 100644 .air.toml create mode 100644 configs/Elastic.go create mode 100644 internal/client/elastic/elastic.go create mode 100644 internal/transport/handler/search.go create mode 100644 internal/transport/router/search.go create mode 100644 service/searchService.go diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..5ff19f9 --- /dev/null +++ b/.air.toml @@ -0,0 +1,51 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./out/cybertron" + cmd = "go mod tidy && CGO_ENABLED=1 go build -ldflags=\"-s -w\" -o out/cybertron cmd/cybertron/main.go" + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = false + +[misc] + clean_on_exit = false + +[proxy] + app_port = 0 + enabled = false + proxy_port = 0 + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/.gitignore b/.gitignore index f8d4a9c..c49a829 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,5 @@ out/ vendor/ go.sum + +tmp/ diff --git a/configs/Elastic.go b/configs/Elastic.go new file mode 100644 index 0000000..b6ed014 --- /dev/null +++ b/configs/Elastic.go @@ -0,0 +1,19 @@ +package configs + +type ElasticConfig struct { + Addresses []string + Username string + Password string + Index string + APIKey string +} + +func NewElasticConfig() *ElasticConfig { + return &ElasticConfig{ + Addresses: getStringSlice("elastic.addresses", true), + Username: getString("elastic.username", true), + Password: getString("elastic.password", true), + Index: getString("elastic.index", true), + APIKey: getString("elastic.api_key", true), + } +} diff --git a/configs/application.yml b/configs/application.yml index 0b505f0..c27e716 100644 --- a/configs/application.yml +++ b/configs/application.yml @@ -42,7 +42,7 @@ http: # Kafka config kafka: - password: xxxa + password: kDia1uC.GI;)Al5eQ)+Q username: varnitgoyal/varnitgoyal95@gmail.com/ocid1.streampool.oc1.ap-mumbai-1.amaaaaaaotdslraanepwp54txqqxkmg4l6dghrhufiezqkx2lqhndgxoq7pa brokers: cell-1.streaming.ap-mumbai-1.oci.oraclecloud.com:9092 group: @@ -66,6 +66,14 @@ DocumentService: mock: generate_token: DOCUMENT_SERVICE_MOCK_GENERATE_TOKEN +elastic: + addresses: https://localhost:9200 + username: elastic + password: 9457611267 + index: cybertron + api_key: U3NUSmFKRUJYaHF5bTJkOUozUU06Z1ZDTE9hZm1RUnlXeHRNY21yeGxfQQ== + + aws: region: ap-south-1 diff --git a/configs/config.go b/configs/config.go index 7f9f8fd..33e4b16 100644 --- a/configs/config.go +++ b/configs/config.go @@ -20,6 +20,7 @@ type AppConfig struct { clientConfigs *ClientConfigs awsConfig *AwsConfig KafkaConfig *KafkaConfig + ElasticConfig *ElasticConfig } type MigConfig struct { @@ -45,6 +46,7 @@ func LoadConfig() { httpConfig: NewHttpConfig(), awsConfig: NewAWSConfig(), KafkaConfig: NewKafkaConfig(), + ElasticConfig: NewElasticConfig(), } } @@ -106,3 +108,7 @@ func GetHttpConfig() *HttpConfig { func GetKafkaConfig() *KafkaConfig { return appConfig.KafkaConfig } + +func GetElasticConfig() *ElasticConfig { + return appConfig.ElasticConfig +} diff --git a/go.mod b/go.mod index 580ea70..fc86b5f 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.27.27 github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 github.com/confluentinc/confluent-kafka-go/v2 v2.5.0 + github.com/elastic/go-elasticsearch/v8 v8.14.0 + github.com/gin-contrib/cors v1.7.2 github.com/gin-contrib/zap v0.2.0 github.com/gin-gonic/gin v1.9.1 github.com/golang-migrate/migrate/v4 v4.17.1 @@ -16,7 +18,7 @@ require ( github.com/xdg-go/scram v1.1.1 go.elastic.co/ecszap v1.0.2 go.uber.org/zap v1.26.0 - google.golang.org/protobuf v1.33.0 + google.golang.org/protobuf v1.34.0 gorm.io/driver/postgres v1.5.3 gorm.io/gorm v1.25.5 ) @@ -39,16 +41,20 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect github.com/aws/smithy-go v1.20.3 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bytedance/sonic v1.10.2 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/elastic/elastic-transport-go/v8 v8.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.15.5 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -62,15 +68,15 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.8 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.48.0 // indirect @@ -84,17 +90,20 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.11 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/stringprep v1.0.3 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/arch v0.5.0 // indirect + golang.org/x/arch v0.7.0 // indirect golang.org/x/crypto v0.22.0 // indirect golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/internal/client/elastic/elastic.go b/internal/client/elastic/elastic.go new file mode 100644 index 0000000..23442d7 --- /dev/null +++ b/internal/client/elastic/elastic.go @@ -0,0 +1,97 @@ +package elastic + +import ( + "context" + "crypto/tls" + "cybertron/configs" + "encoding/json" + elasticsearch8 "github.com/elastic/go-elasticsearch/v8" + "github.com/elastic/go-elasticsearch/v8/typedapi/core/search" + "github.com/elastic/go-elasticsearch/v8/typedapi/types" + "log" + "net/http" +) + +type ElasticSearchClient struct { + client *elasticsearch8.TypedClient + Config configs.ElasticConfig +} + +func NewElasticClient(elasticConfig configs.ElasticConfig) (*ElasticSearchClient, error) { + cfg := elasticsearch8.Config{ + Addresses: elasticConfig.Addresses, + APIKey: elasticConfig.APIKey, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + } + + client, err := elasticsearch8.NewTypedClient(cfg) + return &ElasticSearchClient{ + client: client, + Config: elasticConfig, + }, err +} + +func (el *ElasticSearchClient) IndexDocument(document interface{}) { + _, err := el.client.Index(el.Config.Index). + Request(document). + Do(context.TODO()) + + if err != nil { + log.Fatal(err) + } +} + +func (el *ElasticSearchClient) SearchDocuments(query *types.Query, fields []string) ([]map[string]interface{}, error) { + res, err := el.client.Search(). + Index(el.Config.Index). + Request(&search.Request{ + Query: query, + }).Source_(fields). + Do(context.TODO()) + if err != nil { + log.Println("Error getting response: %s", err) + } + var results []map[string]interface{} + for _, hit := range res.Hits.Hits { + var doc map[string]interface{} + + err := json.Unmarshal(hit.Source_, &doc) + doc["id"] = *hit.Id_ + if err != nil { + log.Printf("Error unmarshalling document: %s", err) + continue + } + results = append(results, doc) + } + + return results, nil +} + +func (el *ElasticSearchClient) GetDocument(documentID string) (interface{}, error) { + // Retrieve the document by its ID + res, err := el.client.Get(el.Config.Index, documentID).Do(context.Background()) + var document interface{} + if err != nil { + log.Printf("Error getting response: %s", err) + return nil, err + } + + // Check if the document exists + if !res.Found { + log.Printf("Document with ID %s not found", documentID) + return document, nil + } + + // Unmarshal the JSON response into the provided document interface + err = json.Unmarshal(res.Source_, &document) + if err != nil { + log.Printf("Error unmarshalling document: %s", err) + return nil, err + } + + return document, nil +} diff --git a/internal/dependencies/dependencies.go b/internal/dependencies/dependencies.go index 59cd237..0af925c 100644 --- a/internal/dependencies/dependencies.go +++ b/internal/dependencies/dependencies.go @@ -4,6 +4,7 @@ import ( "cybertron/configs" "cybertron/internal/client/aws" "cybertron/internal/client/document" + "cybertron/internal/client/elastic" "cybertron/internal/database" "cybertron/internal/transport/handler" "cybertron/pkg/db" @@ -30,6 +31,7 @@ type Service struct { SourceMapService *service.SourceMapService ReleaseService *service.ReleaseService ExceptionService *service.ExceptionService + SearchService *service.SearchService S3Client *aws.Actions // Add your service here } @@ -39,6 +41,7 @@ type Handler struct { SourceMapHandler *handler.SourceMapHandler ReleaseHandler *handler.ReleasesHandler ExceptionHandler *handler.ExceptionHandler + SearchHandler *handler.SearchHandler } type Repositories struct { @@ -54,15 +57,17 @@ func InitDependencies() *Dependencies { httpClient := httpclient.NewHttpClient(*configs.GetHttpConfig()) s3Client := aws.NewS3client(*configs.GetAWSConfig()) kafkaProducer := initKafkaProducer() + elasticSearch, _ := elastic.NewElasticClient(*configs.GetElasticConfig()) documentServiceClient := document.NewDocumentServiceHttpClient(httpClient, logger, configs.GetDocumentServiceHttpClientConfigs()) projectServiceClient := service.NewProjectCreator(logger, dbClient, s3Client, kafkaProducer) sourceMapServiceClient := service.NewSourceMapService(dbClient, s3Client, configs.GetAWSConfig()) releaseServiceClient := service.NewReleaseService(logger, dbClient) exceptionServiceClient := service.NewExceptionService(logger, dbClient, kafkaProducer) + searchServiceClient := service.NewSearchService(logger, elasticSearch) - services := initServices(documentServiceClient, projectServiceClient, sourceMapServiceClient, releaseServiceClient, exceptionServiceClient) - handlers := initHandlers(projectServiceClient, sourceMapServiceClient, releaseServiceClient, exceptionServiceClient) + services := initServices(documentServiceClient, projectServiceClient, sourceMapServiceClient, releaseServiceClient, exceptionServiceClient, searchServiceClient) + handlers := initHandlers(projectServiceClient, sourceMapServiceClient, releaseServiceClient, exceptionServiceClient, searchServiceClient) return &Dependencies{ Service: services, DBClient: dbClient, @@ -72,13 +77,14 @@ func InitDependencies() *Dependencies { } } -func initServices(documentService *document.HttpClient, projectService *service.ProjectCreator, sourceMapService *service.SourceMapService, releaseService *service.ReleaseService, exceptionService *service.ExceptionService) *Service { +func initServices(documentService *document.HttpClient, projectService *service.ProjectCreator, sourceMapService *service.SourceMapService, releaseService *service.ReleaseService, exceptionService *service.ExceptionService, searchService *service.SearchService) *Service { return &Service{ DocumentService: documentService, ProjectService: projectService, SourceMapService: sourceMapService, ReleaseService: releaseService, ExceptionService: exceptionService, + SearchService: searchService, } } @@ -90,16 +96,18 @@ func initRepositories(dbClient *gorm.DB) *Repositories { } } -func initHandlers(projectService *service.ProjectCreator, sourceMapService *service.SourceMapService, releaseService *service.ReleaseService, exceotionService *service.ExceptionService) *Handler { +func initHandlers(projectService *service.ProjectCreator, sourceMapService *service.SourceMapService, releaseService *service.ReleaseService, exceotionService *service.ExceptionService, searchService *service.SearchService) *Handler { projectHandler := handler.NewProjectHandler(projectService) sourceMapHandler := handler.NewSourceMapHandler(sourceMapService) releaseHandler := handler.NewReleaseHandler(releaseService) exceptionHandler := handler.NewExceptionHandler(exceotionService) + searchHandler := handler.NewSearchHandler(searchService) return &Handler{ ProjectHandler: projectHandler, SourceMapHandler: sourceMapHandler, ReleaseHandler: releaseHandler, ExceptionHandler: exceptionHandler, + SearchHandler: searchHandler, } } diff --git a/internal/transport/handler/search.go b/internal/transport/handler/search.go new file mode 100644 index 0000000..6286c6a --- /dev/null +++ b/internal/transport/handler/search.go @@ -0,0 +1,27 @@ +package handler + +import ( + "cybertron/service" + "github.com/gin-gonic/gin" +) + +type SearchHandler struct { + searchService *service.SearchService +} + +func (h *SearchHandler) SearchErrors(c *gin.Context) { + h.searchService.GetErrorDetails(c) +} +func (h *SearchHandler) GetErrorDetails(c *gin.Context) { + h.searchService.GetErrorDetails(c) +} + +func (h *SearchHandler) GetErrorList(c *gin.Context) { + h.searchService.GetErrorList(c) +} + +func NewSearchHandler(s *service.SearchService) *SearchHandler { + return &SearchHandler{ + searchService: s, + } +} diff --git a/internal/transport/router/search.go b/internal/transport/router/search.go new file mode 100644 index 0000000..e35a69b --- /dev/null +++ b/internal/transport/router/search.go @@ -0,0 +1,17 @@ +package router + +import ( + "cybertron/internal/dependencies" + "cybertron/internal/transport/handler" + "github.com/gin-gonic/gin" +) + +func SearchRouter(r *gin.Engine, dep *dependencies.Dependencies) { + searchHandler := handler.NewSearchHandler(dep.Service.SearchService) + searchRouterGroup := r.Group("/api/v1") + { + searchRouterGroup.GET("/errors-list", searchHandler.GetErrorList) + searchRouterGroup.GET("/error-detail", searchHandler.GetErrorDetails) + } + +} diff --git a/internal/transport/server.go b/internal/transport/server.go index 6bb643f..84c80f9 100644 --- a/internal/transport/server.go +++ b/internal/transport/server.go @@ -3,6 +3,7 @@ package transport import ( "cybertron/internal/transport/router" "fmt" + "github.com/gin-contrib/cors" "os" "os/signal" "syscall" @@ -34,10 +35,19 @@ func (s *Server) router() { router.SourceMapRouter(s.gin, s.dependencies) router.ReleasesRouter(s.gin, s.dependencies) router.ExceptionRouter(s.gin, s.dependencies) + router.SearchRouter(s.gin, s.dependencies) } func (s *Server) Start() { s.gin.Use(ginzap.Ginzap(s.dependencies.Logger, time.RFC3339, false)) + s.gin.Use(cors.New(cors.Config{ + AllowOrigins: []string{"*"}, + AllowMethods: []string{"PUT", "PATCH", "GET", "POST", "OPTIONS"}, + AllowHeaders: []string{"*", "x-session-token"}, + ExposeHeaders: []string{"Content-Length"}, + AllowCredentials: true, + MaxAge: 24 * time.Hour, + })) s.router() port := configs.GetPort() diff --git a/service/searchService.go b/service/searchService.go new file mode 100644 index 0000000..0c72508 --- /dev/null +++ b/service/searchService.go @@ -0,0 +1,49 @@ +package service + +import ( + "cybertron/internal/client/elastic" + "cybertron/pkg/log" + "github.com/elastic/go-elasticsearch/v8/typedapi/types" + "github.com/gin-gonic/gin" + "net/http" +) + +type SearchService struct { + logger *log.Logger + elasticSearchClient *elastic.ElasticSearchClient +} + +func NewSearchService(logger *log.Logger, elasticSearchClient *elastic.ElasticSearchClient) *SearchService { + return &SearchService{ + logger: logger, + elasticSearchClient: elasticSearchClient, + } +} + +func (s *SearchService) Search(c *gin.Context) { + +} + +func (s *SearchService) GetErrorDetails(c *gin.Context) { + documentId := c.Query("document_id") + response, _ := s.elasticSearchClient.GetDocument(documentId) + c.JSON(http.StatusOK, response) +} + +func (s *SearchService) GetErrorList(c *gin.Context) { + //todo pagination and aggregation of errors + projectId := c.Query("project_id") + println(projectId) + query := &types.Query{ + Term: map[string]types.TermQuery{ + "project_id": { + Value: projectId, + }, + }, + } + fields := []string{"error", "significant_stack", "title"} + var response, _ = s.elasticSearchClient.SearchDocuments(query, fields) + + c.JSON(http.StatusOK, response) + +} From f537e30cac6243d9c656f5d5995f0660a1026f1d Mon Sep 17 00:00:00 2001 From: Lokesh Dugar Date: Tue, 27 Aug 2024 13:40:31 +0530 Subject: [PATCH 2/2] TP-61307 | Auth middileware and relase service changes --- configs/application.yml | 7 +- configs/config.go | 6 ++ configs/mjolnir_config.go | 22 ++++++ constants/common.go | 6 ++ internal/dependencies/dependencies.go | 10 ++- .../middleware/permission_middleware.go | 52 +++++++++++++ internal/transport/server.go | 2 + models/db/project.go | 2 +- models/db/release.go | 13 +++- pkg/mjolnirClient/mjolnirClient.go | 78 +++++++++++++++++++ service/AuthService.go | 34 ++++++++ service/ReleaseService.go | 15 ++-- service/sourceMap.go | 12 +-- 13 files changed, 237 insertions(+), 22 deletions(-) create mode 100644 configs/mjolnir_config.go create mode 100644 constants/common.go create mode 100644 internal/transport/middleware/permission_middleware.go create mode 100644 pkg/mjolnirClient/mjolnirClient.go create mode 100644 service/AuthService.go diff --git a/configs/application.yml b/configs/application.yml index c27e716..c72066a 100644 --- a/configs/application.yml +++ b/configs/application.yml @@ -73,8 +73,11 @@ elastic: index: cybertron api_key: U3NUSmFKRUJYaHF5bTJkOUozUU06Z1ZDTE9hZm1RUnlXeHRNY21yeGxfQQ== - - aws: region: ap-south-1 bucket: navi-cd955a63c4476df0f00c1cea0e4a40d1 + +#mjolnir config +mjolnir: + service.url: https://qa-mjolnir-service.np.navi-ppl.in + realm.id: O3G7sCWk4r diff --git a/configs/config.go b/configs/config.go index 33e4b16..41f6750 100644 --- a/configs/config.go +++ b/configs/config.go @@ -21,6 +21,7 @@ type AppConfig struct { awsConfig *AwsConfig KafkaConfig *KafkaConfig ElasticConfig *ElasticConfig + mjolnir *MjolnirClientConfig } type MigConfig struct { @@ -47,6 +48,7 @@ func LoadConfig() { awsConfig: NewAWSConfig(), KafkaConfig: NewKafkaConfig(), ElasticConfig: NewElasticConfig(), + mjolnir: NewMjolnirConfig(), } } @@ -112,3 +114,7 @@ func GetKafkaConfig() *KafkaConfig { func GetElasticConfig() *ElasticConfig { return appConfig.ElasticConfig } + +func GetMjolnirConfig() *MjolnirClientConfig { + return appConfig.mjolnir +} diff --git a/configs/mjolnir_config.go b/configs/mjolnir_config.go new file mode 100644 index 0000000..f5887c6 --- /dev/null +++ b/configs/mjolnir_config.go @@ -0,0 +1,22 @@ +package configs + +type MjolnirClientConfig struct { + baseUrl string + realmId string +} + +func NewMjolnirConfig() *MjolnirClientConfig { + + return &MjolnirClientConfig{ + baseUrl: getString("mjolnir.service.url", true), + realmId: getString("mjolnir.realm.id", true), + } +} + +func (p *MjolnirClientConfig) GetMjolnirBaseUrl() string { + return p.baseUrl +} + +func (p *MjolnirClientConfig) GetMjolnirRealmId() string { + return p.realmId +} diff --git a/constants/common.go b/constants/common.go new file mode 100644 index 0000000..2ae262e --- /dev/null +++ b/constants/common.go @@ -0,0 +1,6 @@ +package constants + +const ( + SESSION_HEADER_NAME = "x-session-token" + EMAIL_HEADER_NAME = "x-user-email" +) diff --git a/internal/dependencies/dependencies.go b/internal/dependencies/dependencies.go index 0af925c..624bcce 100644 --- a/internal/dependencies/dependencies.go +++ b/internal/dependencies/dependencies.go @@ -11,6 +11,7 @@ import ( httpclient "cybertron/pkg/httpClient" "cybertron/pkg/kafka/producer" "cybertron/pkg/log" + "cybertron/pkg/mjolnirClient" "cybertron/service" "go.uber.org/zap" @@ -31,6 +32,7 @@ type Service struct { SourceMapService *service.SourceMapService ReleaseService *service.ReleaseService ExceptionService *service.ExceptionService + AuthService *service.AuthService SearchService *service.SearchService S3Client *aws.Actions // Add your service here @@ -58,6 +60,7 @@ func InitDependencies() *Dependencies { s3Client := aws.NewS3client(*configs.GetAWSConfig()) kafkaProducer := initKafkaProducer() elasticSearch, _ := elastic.NewElasticClient(*configs.GetElasticConfig()) + mjolnirClient := mjolnirClient.NewMjolnirClient(*configs.GetMjolnirConfig()) documentServiceClient := document.NewDocumentServiceHttpClient(httpClient, logger, configs.GetDocumentServiceHttpClientConfigs()) projectServiceClient := service.NewProjectCreator(logger, dbClient, s3Client, kafkaProducer) @@ -65,9 +68,11 @@ func InitDependencies() *Dependencies { releaseServiceClient := service.NewReleaseService(logger, dbClient) exceptionServiceClient := service.NewExceptionService(logger, dbClient, kafkaProducer) searchServiceClient := service.NewSearchService(logger, elasticSearch) + authService := service.NewAuthService(mjolnirClient) - services := initServices(documentServiceClient, projectServiceClient, sourceMapServiceClient, releaseServiceClient, exceptionServiceClient, searchServiceClient) + services := initServices(documentServiceClient, projectServiceClient, sourceMapServiceClient, releaseServiceClient, exceptionServiceClient, searchServiceClient, authService) handlers := initHandlers(projectServiceClient, sourceMapServiceClient, releaseServiceClient, exceptionServiceClient, searchServiceClient) + return &Dependencies{ Service: services, DBClient: dbClient, @@ -77,7 +82,7 @@ func InitDependencies() *Dependencies { } } -func initServices(documentService *document.HttpClient, projectService *service.ProjectCreator, sourceMapService *service.SourceMapService, releaseService *service.ReleaseService, exceptionService *service.ExceptionService, searchService *service.SearchService) *Service { +func initServices(documentService *document.HttpClient, projectService *service.ProjectCreator, sourceMapService *service.SourceMapService, releaseService *service.ReleaseService, exceptionService *service.ExceptionService, searchService *service.SearchService, authService *service.AuthService) *Service { return &Service{ DocumentService: documentService, ProjectService: projectService, @@ -85,6 +90,7 @@ func initServices(documentService *document.HttpClient, projectService *service. ReleaseService: releaseService, ExceptionService: exceptionService, SearchService: searchService, + AuthService: authService, } } diff --git a/internal/transport/middleware/permission_middleware.go b/internal/transport/middleware/permission_middleware.go new file mode 100644 index 0000000..e8a2d3f --- /dev/null +++ b/internal/transport/middleware/permission_middleware.go @@ -0,0 +1,52 @@ +package middleware + +import ( + "cybertron/constants" + "cybertron/service" + "net/http" + + "github.com/gin-gonic/gin" +) + +type UserInfo struct { + SessionToken string `json:"sessionToken"` + ClientID string `json:"clientId"` + Name string `json:"name"` + Exp int `json:"exp"` + EmailID string `json:"emailId"` + AccountID string `json:"accountId"` + PhoneNumber string `json:"phoneNumber"` + Roles []string `json:"roles"` + Groups []string `json:"groups"` + Permissions []string `json:"permissions"` + FirebaseJwtToken string `json:"firebaseJwtToken"` + FirebaseNode string `json:"firebaseNode"` + ProfilePictureURL string `json:"profilePictureUrl"` + PreferredUsername string `json:"preferred_username"` +} + +func PermissionMiddleware(authService *service.AuthService) gin.HandlerFunc { + return func(c *gin.Context) { + + sessionToken := c.GetHeader(constants.SESSION_HEADER_NAME) + userEmail := c.GetHeader(constants.EMAIL_HEADER_NAME) + + validUser, err := authService.CheckValidUser(c, sessionToken, userEmail) + if err != nil || !validUser { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + c.Abort() + return + } + + c.Next() + } +} + +func isAdmin(roles []string) bool { + for _, role := range roles { + if role == "Admin" { + return true + } + } + return false +} diff --git a/internal/transport/server.go b/internal/transport/server.go index 84c80f9..6b87d50 100644 --- a/internal/transport/server.go +++ b/internal/transport/server.go @@ -1,6 +1,7 @@ package transport import ( + "cybertron/internal/transport/middleware" "cybertron/internal/transport/router" "fmt" "github.com/gin-contrib/cors" @@ -48,6 +49,7 @@ func (s *Server) Start() { AllowCredentials: true, MaxAge: 24 * time.Hour, })) + s.gin.Use(middleware.PermissionMiddleware(s.dependencies.Service.AuthService)) s.router() port := configs.GetPort() diff --git a/models/db/project.go b/models/db/project.go index 1823dcc..fe04ad3 100644 --- a/models/db/project.go +++ b/models/db/project.go @@ -5,7 +5,7 @@ import ( ) type Project struct { - ID int `json:"id"` + ID int `json:"id" gorm:"autoIncrement:true"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` DeletedAt time.Time `json:"deletedAt"` diff --git a/models/db/release.go b/models/db/release.go index 43eab6d..b02f6e0 100644 --- a/models/db/release.go +++ b/models/db/release.go @@ -1,9 +1,14 @@ package db -import "gorm.io/gorm" +import ( + "time" +) type Release struct { - gorm.Model - ProjectReferenceId string `gorm:"primaryKey"` - ReleaseVersion string `gorm:"column:name"` + ID int64 `json:"id" gorm:"autoIncrement:true"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt time.Time `json:"deletedAt"` + ProjectReferenceId string `json:"projectReferenceId" gorm:"primaryKey"` + ReleaseVersion string `json:"releaseVersion" gorm:"primaryKey"` } diff --git a/pkg/mjolnirClient/mjolnirClient.go b/pkg/mjolnirClient/mjolnirClient.go new file mode 100644 index 0000000..aee186b --- /dev/null +++ b/pkg/mjolnirClient/mjolnirClient.go @@ -0,0 +1,78 @@ +package mjolnirClient + +import ( + "cybertron/configs" + "cybertron/pkg/log" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" +) + +type MjolnirClient struct { + baseUrl string + realmId string +} + +type MjolnirError struct { + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` + Params interface{} `json:"params,omitempty"` +} + +type MjolnirSessionResponse struct { + SessionToken string `json:"sessionToken,omitempty"` + ClientId string `json:"clientId,omitempty"` + EmailId string `json:"emailId,omitempty"` + AccountId string `json:"accountId,omitempty"` + PhoneNumber string `json:"phoneNumber,omitempty"` + PreferredUsername string `json:"preferred_username,omitempty"` + Roles []string `json:"roles,omitempty"` + Groups []string `json:"groups,omitempty"` + Permissions []string `json:"permissions,omitempty"` + StatusCode int `json:"statusCode,omitempty"` + Errors []MjolnirError `json:"errors"` +} + +func NewMjolnirClient(mjolnirConfig configs.MjolnirClientConfig) *MjolnirClient { + return &MjolnirClient{ + baseUrl: mjolnirConfig.GetMjolnirBaseUrl(), + realmId: mjolnirConfig.GetMjolnirRealmId(), + } +} + +var logger = log.Log.GetLog() +var client = http.Client{} + +const ( + SessionUrl = "%s/session/%s" +) + +func (m *MjolnirClient) GetSessionResponse(sessionToken string) (*MjolnirSessionResponse, error) { + if sessionToken == "null" { + return nil, errors.New("unauthorized request") + } + + req, _ := http.NewRequest("GET", fmt.Sprintf(SessionUrl, m.baseUrl, m.realmId), nil) + req.Header.Add("X-Session-Token", sessionToken) + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var response MjolnirSessionResponse + err = json.Unmarshal(responseBody, &response) + if err != nil { + return nil, err + } + logger.Info(fmt.Sprintf("%v", response)) + + return &response, nil +} diff --git a/service/AuthService.go b/service/AuthService.go new file mode 100644 index 0000000..d1d218a --- /dev/null +++ b/service/AuthService.go @@ -0,0 +1,34 @@ +package service + +import ( + "cybertron/pkg/mjolnirClient" + "fmt" + "github.com/gin-gonic/gin" + "net/http" +) + +type AuthService struct { + mjolnirClient *mjolnirClient.MjolnirClient +} + +func NewAuthService(mjolnirClient *mjolnirClient.MjolnirClient) *AuthService { + return &AuthService{ + mjolnirClient: mjolnirClient, + } +} + +type UserRoles string + +func (authService *AuthService) CheckValidUser(c *gin.Context, sessionToken, userEmail string) (bool, error) { + sessionResponse, err := authService.mjolnirClient.GetSessionResponse(sessionToken) + if err != nil || sessionResponse.StatusCode == http.StatusUnauthorized { + return false, err + } + if sessionResponse.EmailId != userEmail { + return false, fmt.Errorf("user email: %v does not match the email linked to the session token", userEmail) + } + + c.Set("permissions", sessionResponse.Permissions) + + return true, nil +} diff --git a/service/ReleaseService.go b/service/ReleaseService.go index de0cec7..c5085df 100644 --- a/service/ReleaseService.go +++ b/service/ReleaseService.go @@ -14,9 +14,8 @@ type ReleaseService struct { } type ReleaseBody struct { - ProjectId string `json:"project_id" binding:"required"` - Version string `json:"version" binding:"required"` - SourceMapUrl string `json:"source_map_url" binding:"required"` + ProjectReferenceId string `json:"projectReferenceId" binding:"required"` + ReleaseVersion string `json:"releaseVersion" binding:"required"` } func NewReleaseService(logger *log.Logger, dbClient *gorm.DB) *ReleaseService { @@ -26,7 +25,6 @@ func NewReleaseService(logger *log.Logger, dbClient *gorm.DB) *ReleaseService { } } -// FIXME: This may not be requried now, can be used in source maps services only func (releaseService *ReleaseService) AddRelease(c *gin.Context) { var releaseBody ReleaseBody if err := c.BindJSON(&releaseBody); err != nil { @@ -37,8 +35,8 @@ func (releaseService *ReleaseService) AddRelease(c *gin.Context) { } releaseToBeAdded := db.Release{ - ProjectReferenceId: releaseBody.ProjectId, - ReleaseVersion: releaseBody.Version, + ProjectReferenceId: releaseBody.ProjectReferenceId, + ReleaseVersion: releaseBody.ReleaseVersion, } if result := releaseService.dbClient.Create(&releaseToBeAdded); result.Error != nil { @@ -53,11 +51,12 @@ func (releaseService *ReleaseService) AddRelease(c *gin.Context) { func (releaseService *ReleaseService) GetReleases(c *gin.Context) { projectRefId := c.Query("project_reference_id") + releaseVersion := c.Query("release_version") var releases []db.Release - releaseService.dbClient.Where("project_reference_id = ?", projectRefId).Find(&releases) + result := releaseService.dbClient.Where("project_reference_id = ? OR release_version = ?", projectRefId, releaseVersion).Find(&releases) - if result := releaseService.dbClient.Find(&releases); result.Error != nil { + if result.Error != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": result.Error.Error()}) return } diff --git a/service/sourceMap.go b/service/sourceMap.go index 7b7c13b..e3d2efc 100644 --- a/service/sourceMap.go +++ b/service/sourceMap.go @@ -21,9 +21,9 @@ type SourceMapService struct { } type SourceMapAckBody struct { - ProjectId string `json:"project-id" binding:"required"` - ReleaseId string `json:"releaseId" binding:"required"` - FileName string `json:"file_name" binding:"required"` + ProjectReferenceId string `json:"projectId" binding:"required"` + ReleaseId string `json:"releaseId" binding:"required"` + FileName string `json:"fileName" binding:"required"` } func NewSourceMapService(dbClient *gorm.DB, s3Client *aws.Actions, config *configs.AwsConfig) *SourceMapService { @@ -45,10 +45,11 @@ func (s *SourceMapService) GetSourceMapUploadUrl(ctx *gin.Context) { }) return } + //generate s3 pre-signed url key := path.Join(projectId, releaseId, fileName) bucket := s.awsConfig.Bucket - request, err := s.s3Client.S3PresignClient.PresignGetObject(context.TODO(), &s3.GetObjectInput{ + request, err := s.s3Client.S3PresignClient.PresignPutObject(context.TODO(), &s3.PutObjectInput{ Bucket: &bucket, Key: &key, }, func(opts *s3.PresignOptions) { @@ -83,9 +84,10 @@ func (s *SourceMapService) SourceMapUploadAck(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } + //find the record to update ack var existingSourceMap db.SourceMap - existingRecordError := s.dbClient.First(&existingSourceMap, "project_reference_id = ? and release_reference_id= ? and file_name = ?", sourceMapAckBody.ProjectId, sourceMapAckBody.ReleaseId, sourceMapAckBody.FileName).Error + existingRecordError := s.dbClient.First(&existingSourceMap, "project_reference_id = ? and release_reference_id= ? and file_name = ?", sourceMapAckBody.ProjectReferenceId, sourceMapAckBody.ReleaseId, sourceMapAckBody.FileName).Error if existingRecordError != nil { c.JSON(http.StatusBadRequest, gin.H{"error": existingRecordError.Error()}) return