diff --git a/go.mod b/go.mod index c0fb2b20..84f9b03b 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/rs/cors v1.7.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/viper v1.10.1 + github.com/statsig-io/go-sdk v1.26.0 github.com/stretchr/testify v1.8.4 github.com/twmb/franz-go v1.17.0 github.com/twmb/franz-go/pkg/kmsg v1.8.0 @@ -99,8 +100,10 @@ require ( github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/statsig-io/ip3country-go v0.2.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/tinylib/msgp v1.1.8 // indirect + github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f // indirect go.uber.org/atomic v1.11.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.20.0 // indirect diff --git a/go.sum b/go.sum index a635b084..f5242779 100644 --- a/go.sum +++ b/go.sum @@ -1909,6 +1909,10 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= +github.com/statsig-io/go-sdk v1.26.0 h1:HaPwN1+mDFdCnvqDbo8cr3obIlqmrShR9J2ogxw3kZs= +github.com/statsig-io/go-sdk v1.26.0/go.mod h1:Pej0D6R75gTHj7FdS6pbXQ7ayF0HL1cwOgiz5zDNdyc= +github.com/statsig-io/ip3country-go v0.2.0 h1:4z4ovVCx7GnQAKJC753bjcOgxLQJFsrDdcCKda4I2U8= +github.com/statsig-io/ip3country-go v0.2.0/go.mod h1:PKuA/VSpe4puBXw3BNGAHyP8IOZOiXAh/xIz+iYYoMQ= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1961,6 +1965,8 @@ github.com/twmb/franz-go v1.17.0 h1:hawgCx5ejDHkLe6IwAtFWwxi3OU4OztSTl7ZV5rwkYk= github.com/twmb/franz-go v1.17.0/go.mod h1:NreRdJ2F7dziDY/m6VyspWd6sNxHKXdMZI42UfQ3GXM= github.com/twmb/franz-go/pkg/kmsg v1.8.0 h1:lAQB9Z3aMrIP9qF9288XcFf/ccaSxEitNA1CDTEIeTA= github.com/twmb/franz-go/pkg/kmsg v1.8.0/go.mod h1:HzYEb8G3uu5XevZbtU0dVbkphaKTHk0X68N5ka4q6mU= +github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f h1:A+MmlgpvrHLeUP8dkBVn4Pnf5Bp5Yk2OALm7SEJLLE8= +github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f/go.mod h1:OBcG9bn7sHtXgarhUEb3OfCnNsgtGnkVf41ilSZ3K3E= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index 6d5cc130..9188d400 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -11,6 +11,7 @@ import ( logger "github.com/scribd/go-sdk/pkg/logger" "github.com/scribd/go-sdk/pkg/pubsub" server "github.com/scribd/go-sdk/pkg/server" + "github.com/scribd/go-sdk/pkg/statsig" tracking "github.com/scribd/go-sdk/pkg/tracking" ) @@ -25,6 +26,7 @@ type Config struct { PubSub *pubsub.Config Cache *cache.Config AWS *aws.Config + Statsig *statsig.Config } // NewConfig returns a new Config instance @@ -77,6 +79,11 @@ func NewConfig() (*Config, error) { errGroup = wrapErrors(errGroup, fmt.Errorf("aws config err: %w", err)) } + statsigConfig, err := statsig.NewConfig() + if err != nil { + errGroup = wrapErrors(errGroup, fmt.Errorf("statsig config err: %w", err)) + } + config.App = appConfig config.Database = dbConfig config.Instrumentation = instrumentationConfig @@ -86,6 +93,7 @@ func NewConfig() (*Config, error) { config.PubSub = pubsubConfig config.Cache = cacheConfig config.AWS = awsConfig + config.Statsig = statsigConfig return config, errGroup } diff --git a/pkg/context/statsig/context.go b/pkg/context/statsig/context.go new file mode 100644 index 00000000..0d6df644 --- /dev/null +++ b/pkg/context/statsig/context.go @@ -0,0 +1,36 @@ +package statsig + +import ( + "context" + "fmt" + + statsigsdk "github.com/statsig-io/go-sdk" +) + +type ctxStatsigUserMarker struct{} + +type ctxStatsig struct { + user statsigsdk.User +} + +var ( + ctxStatsigUserKey = &ctxStatsigUserMarker{} +) + +// cmnt me +func Extract(ctx context.Context) (statsigsdk.User, error) { + r, ok := ctx.Value(ctxStatsigUserKey).(*ctxStatsig) + if !ok || r == nil { + return statsigsdk.User{}, fmt.Errorf("unable to get the statsig user") + } + + return r.user, nil +} + +// cmnt me +func ToContext(ctx context.Context, u statsigsdk.User) context.Context { + r := &ctxStatsig{ + user: u, + } + return context.WithValue(ctx, ctxStatsigUserKey, r) +} diff --git a/pkg/statsig/config.go b/pkg/statsig/config.go new file mode 100644 index 00000000..1ad5f47a --- /dev/null +++ b/pkg/statsig/config.go @@ -0,0 +1,38 @@ +package statsig + +import ( + "fmt" + "os" + "time" + + cbuilder "github.com/scribd/go-sdk/internal/pkg/configuration/builder" +) + +// Config stores the configuration for the statsig. +type Config struct { + StatsigDSN string `mapstructure:"dsn"` + LocalMode bool `mapstructure:"local_mode"` + ConfigSyncInterval time.Duration `mapstructure:"config_sync_interval"` + IDListSyncInterval time.Duration `mapstructure:"id_list_sync_interval"` + + environment string +} + +// NewConfig returns a new StatsigConfig instance. +func NewConfig() (*Config, error) { + config := &Config{} + config.environment = os.Getenv("APP_ENV") + + viperBuilder := cbuilder.New("statsig") + + vConf, err := viperBuilder.Build() + if err != nil { + return config, err + } + + if err = vConf.Unmarshal(config); err != nil { + return config, fmt.Errorf("unable to decode into struct: %s", err.Error()) + } + + return config, nil +} diff --git a/pkg/statsig/statsig.go b/pkg/statsig/statsig.go new file mode 100644 index 00000000..1fac2287 --- /dev/null +++ b/pkg/statsig/statsig.go @@ -0,0 +1,46 @@ +package statsig + +import ( + statsigsdk "github.com/statsig-io/go-sdk" +) + +type ( + GetFeatureFlagFunc func(user statsigsdk.User) bool + GetExperimentFunc func(user statsigsdk.User) statsigsdk.DynamicConfig +) + +func Initialize(c *Config) { + opts := &statsigsdk.Options{ + Environment: statsigsdk.Environment{Tier: c.environment}, + } + + if c.LocalMode { + opts.LocalMode = true + } + + if c.ConfigSyncInterval > 0 { + opts.ConfigSyncInterval = c.ConfigSyncInterval + } + + if c.IDListSyncInterval > 0 { + opts.IDListSyncInterval = c.IDListSyncInterval + } + + statsigsdk.InitializeWithOptions(c.StatsigDSN, opts) +} + +func GetExperiment(gate string) GetExperimentFunc { + return func(user statsigsdk.User) statsigsdk.DynamicConfig { + return statsigsdk.GetExperiment(user, gate) + } +} + +func GetFeatureFlag(flag string) GetFeatureFlagFunc { + return func(user statsigsdk.User) bool { + return statsigsdk.GetGate(user, flag).Value + } +} + +func Shutdown() { + statsigsdk.Shutdown() +}