From c6a55b18a8fadfc7eadcf34262d8737c6ea1bbac Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 15 Oct 2024 15:54:06 +0200 Subject: [PATCH] feat(backend): analytics server initial setup --- .gitignore | 2 + backend.iml | 9 ++++ backend/cmd/analytics/main.go | 58 ++++++++++++++++++++ backend/internal/config/analytics/config.go | 36 +++++++++++++ backend/pkg/analytics/api/router.go | 59 +++++++++++++++++++++ backend/pkg/analytics/builder.go | 27 ++++++++++ 6 files changed, 191 insertions(+) create mode 100644 backend.iml create mode 100644 backend/cmd/analytics/main.go create mode 100644 backend/internal/config/analytics/config.go create mode 100644 backend/pkg/analytics/api/router.go create mode 100644 backend/pkg/analytics/builder.go diff --git a/.gitignore b/.gitignore index e363c25f0..0f00ca4eb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ public .cache node_modules +backend/pkg/mod +backend/pkg/sumdb *DS_Store *.env *.log diff --git a/backend.iml b/backend.iml new file mode 100644 index 000000000..d128b532f --- /dev/null +++ b/backend.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/backend/cmd/analytics/main.go b/backend/cmd/analytics/main.go new file mode 100644 index 000000000..747cc5910 --- /dev/null +++ b/backend/cmd/analytics/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "context" + "openreplay/backend/internal/http/server" + "openreplay/backend/pkg/analytics" + "openreplay/backend/pkg/analytics/api" + "openreplay/backend/pkg/db/postgres/pool" + "openreplay/backend/pkg/logger" + "os" + "os/signal" + "syscall" + + config "openreplay/backend/internal/config/analytics" +) + +func main() { + ctx := context.Background() + log := logger.New() + cfg := config.New(log) + + pgConn, err := pool.New(cfg.Postgres.String()) + if err != nil { + log.Fatal(ctx, "can't init postgres connection: %s", err) + } + defer pgConn.Close() + + services, err := analytics.NewServiceBuilder(log, cfg, pgConn) + if err != nil { + log.Fatal(ctx, "can't init services: %s", err) + } + + router, err := api.NewRouter(cfg, log, services) + if err != nil { + log.Fatal(ctx, "failed while creating router: %s", err) + } + + analyticsServer, err := server.New(router.GetHandler(), cfg.HTTPHost, cfg.HTTPPort, cfg.HTTPTimeout) + if err != nil { + log.Fatal(ctx, "failed while creating server: %s", err) + } + + go func() { + if err := analyticsServer.Start(); err != nil { + log.Fatal(ctx, "http server error: %s", err) + } + }() + + log.Info(ctx, "server successfully started on port %s", cfg.HTTPPort) + + // Wait stop signal to shut down server gracefully + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) + <-sigchan + log.Info(ctx, "shutting down the server") + analyticsServer.Stop() + +} diff --git a/backend/internal/config/analytics/config.go b/backend/internal/config/analytics/config.go new file mode 100644 index 000000000..ea44a4842 --- /dev/null +++ b/backend/internal/config/analytics/config.go @@ -0,0 +1,36 @@ +package analytics + +import ( + "time" + + "openreplay/backend/internal/config/common" + "openreplay/backend/internal/config/configurator" + "openreplay/backend/internal/config/objectstorage" + "openreplay/backend/internal/config/redis" + "openreplay/backend/pkg/env" + "openreplay/backend/pkg/logger" +) + +type Config struct { + common.Config + common.Postgres + redis.Redis + objectstorage.ObjectsConfig + FSDir string `env:"FS_DIR,required"` + + HTTPHost string `env:"HTTP_HOST,default="` + HTTPPort string `env:"HTTP_PORT,required"` + HTTPTimeout time.Duration `env:"HTTP_TIMEOUT,default=60s"` + JsonSizeLimit int64 `env:"JSON_SIZE_LIMIT,default=131072"` // 128KB + UseAccessControlHeaders bool `env:"USE_CORS,default=false"` + ProjectExpiration time.Duration `env:"PROJECT_EXPIRATION,default=10m"` + JWTSecret string `env:"JWT_SECRET,required"` + MinimumStreamDuration int `env:"MINIMUM_STREAM_DURATION,default=15000"` // 15s + WorkerID uint16 +} + +func New(log logger.Logger) *Config { + cfg := &Config{WorkerID: env.WorkerID()} + configurator.Process(log, cfg) + return cfg +} diff --git a/backend/pkg/analytics/api/router.go b/backend/pkg/analytics/api/router.go new file mode 100644 index 000000000..7608e84ae --- /dev/null +++ b/backend/pkg/analytics/api/router.go @@ -0,0 +1,59 @@ +package api + +import ( + "fmt" + "github.com/gorilla/mux" + "net/http" + analyticsConfig "openreplay/backend/internal/config/analytics" + "openreplay/backend/pkg/analytics" + "openreplay/backend/pkg/logger" + "sync" +) + +type Router struct { + log logger.Logger + cfg *analyticsConfig.Config + router *mux.Router + mutex *sync.RWMutex + services *analytics.ServicesBuilder +} + +func NewRouter(cfg *analyticsConfig.Config, log logger.Logger, services *analytics.ServicesBuilder) (*Router, error) { + switch { + case cfg == nil: + return nil, fmt.Errorf("config is empty") + case services == nil: + return nil, fmt.Errorf("services is empty") + case log == nil: + return nil, fmt.Errorf("logger is empty") + } + e := &Router{ + log: log, + cfg: cfg, + mutex: &sync.RWMutex{}, + services: services, + } + e.init() + return e, nil +} + +func (e *Router) init() { + e.router = mux.NewRouter() + + // Root route + e.router.HandleFunc("/", e.ping) + + // Analytics routes + // e.router.HandleFunc("/v1/analytics", e.createAnalytics).Methods("POST", "OPTIONS") + // e.router.HandleFunc("/v1/analytics/{id}", e.getAnalytics).Methods("GET", "OPTIONS") + // e.router.HandleFunc("/v1/analytics/{id}", e.updateAnalytics).Methods("PATCH", "OPTIONS") + // e.router.HandleFunc("/v1/analytics", e.getAnalytics).Methods("GET", "OPTIONS") +} + +func (e *Router) ping(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) +} + +func (e *Router) GetHandler() http.Handler { + return e.router +} diff --git a/backend/pkg/analytics/builder.go b/backend/pkg/analytics/builder.go new file mode 100644 index 000000000..12f68513f --- /dev/null +++ b/backend/pkg/analytics/builder.go @@ -0,0 +1,27 @@ +package analytics + +import ( + "openreplay/backend/internal/config/analytics" + "openreplay/backend/pkg/db/postgres/pool" + "openreplay/backend/pkg/flakeid" + "openreplay/backend/pkg/logger" + "openreplay/backend/pkg/objectstorage" + "openreplay/backend/pkg/objectstorage/store" +) + +type ServicesBuilder struct { + Flaker *flakeid.Flaker + ObjStorage objectstorage.ObjectStorage +} + +func NewServiceBuilder(log logger.Logger, cfg *analytics.Config, pgconn pool.Pool) (*ServicesBuilder, error) { + objStore, err := store.NewStore(&cfg.ObjectsConfig) + if err != nil { + return nil, err + } + flaker := flakeid.NewFlaker(cfg.WorkerID) + return &ServicesBuilder{ + Flaker: flaker, + ObjStorage: objStore, + }, nil +}