package serverimp import ( "context" "os" "strings" "time" "github.com/tech/sendico/billing/fees/internal/service/fees" "github.com/tech/sendico/billing/fees/storage" mongostorage "github.com/tech/sendico/billing/fees/storage/mongo" oracleclient "github.com/tech/sendico/fx/oracle/client" "github.com/tech/sendico/pkg/api/routers" "github.com/tech/sendico/pkg/db" msg "github.com/tech/sendico/pkg/messaging" "github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/server/grpcapp" "go.uber.org/zap" "gopkg.in/yaml.v3" ) type Imp struct { logger mlogger.Logger file string debug bool config *config app *grpcapp.App[storage.Repository] oracleClient oracleclient.Client service *fees.Service } type config struct { *grpcapp.Config `yaml:",inline"` Oracle OracleConfig `yaml:"oracle"` } type OracleConfig struct { Address string `yaml:"address"` DialTimeoutSecs int `yaml:"dial_timeout_seconds"` CallTimeoutSecs int `yaml:"call_timeout_seconds"` InsecureTransport bool `yaml:"insecure"` } func (c OracleConfig) dialTimeout() time.Duration { if c.DialTimeoutSecs <= 0 { return 5 * time.Second } return time.Duration(c.DialTimeoutSecs) * time.Second } func (c OracleConfig) callTimeout() time.Duration { if c.CallTimeoutSecs <= 0 { return 3 * time.Second } return time.Duration(c.CallTimeoutSecs) * time.Second } // Create initialises the billing fees server implementation. func Create(logger mlogger.Logger, file string, debug bool) (*Imp, error) { return &Imp{ logger: logger.Named("server"), file: file, debug: debug, }, nil } func (i *Imp) Shutdown() { if i.app == nil { if i.service != nil { i.service.Shutdown() } if i.oracleClient != nil { _ = i.oracleClient.Close() } return } timeout := 15 * time.Second if i.config != nil && i.config.Runtime != nil { timeout = i.config.Runtime.ShutdownTimeout() } if i.service != nil { i.service.Shutdown() } ctx, cancel := context.WithTimeout(context.Background(), timeout) i.app.Shutdown(ctx) cancel() if i.oracleClient != nil { _ = i.oracleClient.Close() } } func (i *Imp) Start() error { cfg, err := i.loadConfig() if err != nil { return err } i.config = cfg repoFactory := func(logger mlogger.Logger, conn *db.MongoConnection) (storage.Repository, error) { return mongostorage.New(logger, conn) } var oracleClient oracleclient.Client if addr := strings.TrimSpace(cfg.Oracle.Address); addr != "" { dialCtx, cancel := context.WithTimeout(context.Background(), cfg.Oracle.dialTimeout()) defer cancel() oc, err := oracleclient.New(dialCtx, oracleclient.Config{ Address: addr, DialTimeout: cfg.Oracle.dialTimeout(), CallTimeout: cfg.Oracle.callTimeout(), Insecure: cfg.Oracle.InsecureTransport, }) if err != nil { i.logger.Warn("failed to initialise oracle client", zap.String("address", addr), zap.Error(err)) } else { oracleClient = oc i.oracleClient = oc i.logger.Info("connected to oracle service", zap.String("address", addr)) } } serviceFactory := func(logger mlogger.Logger, repo storage.Repository, producer msg.Producer) (grpcapp.Service, error) { opts := []fees.Option{} if oracleClient != nil { opts = append(opts, fees.WithOracleClient(oracleClient)) } svc := fees.NewService(logger, repo, producer, opts...) i.service = svc return svc, nil } app, err := grpcapp.NewApp(i.logger, "billing_fees", cfg.Config, i.debug, repoFactory, serviceFactory) if err != nil { return err } i.app = app return i.app.Start() } func (i *Imp) loadConfig() (*config, error) { data, err := os.ReadFile(i.file) if err != nil { i.logger.Error("Could not read configuration file", zap.String("config_file", i.file), zap.Error(err)) return nil, err } cfg := &config{Config: &grpcapp.Config{}} if err := yaml.Unmarshal(data, cfg); err != nil { i.logger.Error("Failed to parse configuration", zap.Error(err)) return nil, err } if cfg.Runtime == nil { cfg.Runtime = &grpcapp.RuntimeConfig{ShutdownTimeoutSeconds: 15} } if cfg.GRPC == nil { cfg.GRPC = &routers.GRPCConfig{ Network: "tcp", Address: ":50060", EnableReflection: true, EnableHealth: true, } } return cfg, nil }