package main import ( "context" "fmt" "gitea.pena/PenaSide/common/encrypt" "gitea.pena/PenaSide/common/mongo" "gitea.pena/PenaSide/customer/internal/models" "github.com/gofiber/fiber/v2" "github.com/twmb/franz-go/pkg/kgo" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "log" "os" "regexp" "strconv" "strings" "time" ) func main() { config, err := loadConfig() if err != nil { log.Fatalf("error loading config: %v", err) } err = validateNotEmpty(config) if err != nil { log.Fatalf("error validating config: %v", err) } urls := []string{ config.AuthMicroservice, config.HubadminMicroservice, config.CurrencyMicroservice, config.DiscountMicroservice, config.CodewordMicroservice, config.PaymentMicroservice, config.VerificationMicroservice, config.TemplategenMicroserviceURL, config.TrashLogHost, config.AdminURL, config.ExternalCfg.MailClientCfg.ApiUrl, } if err = validateURLs(urls); err != nil { log.Fatalf("error validating urls: %v", err) } // todo validate jwt if err = validateKafka(config.KafkaBrokers, config.KafkaTopic); err != nil { log.Fatalf("error validating kafka: %v", err) } if err = validateMail(config.ExternalCfg.MailClientCfg); err != nil { log.Fatalf("error validating smtp: %v", err) } if err = validateEncryptKeys(&config.ExternalCfg.EncryptCommon); err != nil { log.Fatalf("error validating encrypted: %v", err) } if err = validateTG(config.NotificationBotToken, config.NotificationRsPayChannel, config.NotificationChannel); err != nil { log.Fatalf("error validating tg enviroments: %v", err) } if err = validateMongo(config.ExternalCfg.Database); err != nil { log.Fatalf("error validating mongodb: %v", err) } } // 38 fields func loadConfig() (*models.Config, error) { config := models.Config{ ExternalCfg: models.ExternalCfg{ JwtCfg: models.JWTConfiguration{ PublicKey: os.Getenv("JWT_PUBLIC_KEY"), Audience: os.Getenv("JWT_AUDIENCE"), Issuer: os.Getenv("JWT_ISSUER"), }, Database: mongo.Configuration{ URL: os.Getenv("MONGO_URL"), DatabaseName: os.Getenv("MONGO_DB_NAME"), }, MailClientCfg: models.MailClientCfg{ ApiUrl: os.Getenv("API_URL"), Sender: os.Getenv("MAIL_SENDER"), ApiKey: os.Getenv("MAIL_API_KEY"), MailAddress: os.Getenv("MAIL_ADDRESS"), }, EncryptCommon: encrypt.Encrypt{ PrivKey: os.Getenv("ENCRYPT_PRIVATE_KEY"), PubKey: os.Getenv("ENCRYPT_PUBLIC_KEY"), }, }, ClientHttpPort: os.Getenv("CLIENT_HTTP_PORT"), ClientHttpHost: os.Getenv("CLIENT_HTTP_HOST"), AdminHttpPort: os.Getenv("ADMIN_HTTP_PORT"), AdminHttpHost: os.Getenv("ADMIN_HTTP_HOST"), GrpcHost: os.Getenv("GRPC_HOST"), GrpcPort: os.Getenv("GRPC_PORT"), GrpcDomen: os.Getenv("GRPC_DOMEN"), KafkaBrokers: strings.Split(os.Getenv("KAFKA_BROKERS"), ","), KafkaTopic: os.Getenv("KAFKA_TOPIC_TARIFF"), AuthMicroservice: os.Getenv("AUTH_MICROSERVICE_URL"), HubadminMicroservice: os.Getenv("HUBADMIN_MICROSERVICE_URL"), CurrencyMicroservice: os.Getenv("CURRENCY_MICROSERVICE_URL"), DiscountMicroservice: os.Getenv("DISCOUNT_MICROSERVICE_GRPC_HOST"), PaymentMicroservice: os.Getenv("PAYMENT_MICROSERVICE_GRPC_HOST"), VerificationMicroservice: os.Getenv("VERIFICATION_MICROSERVICE_URL"), TemplategenMicroserviceURL: os.Getenv("TEMPLATEGEN_MICROSERVICE_URL"), CodewordMicroservice: os.Getenv("CODEWORD_MICROSERVICE_GRPC_HOST"), TrashLogHost: os.Getenv("TRASH_LOG_HOST"), NotificationBotToken: os.Getenv("NOTIFICATION_BOT_TOKEN"), NotificationRsPayChannel: envToInt64(os.Getenv("NOTIFICATION_RS_PAY_CHANNEL")), NotificationChannel: envToInt64(os.Getenv("NOTIFICATION_CHANNEL")), AdminURL: os.Getenv("ADMIN_FRONT_URL"), } return &config, nil } func envToInt64(str string) int64 { n, err := strconv.ParseInt(str, 10, 64) if err != nil { panic(err) } return n } func validateURLs(urls []string) error { for index, u := range urls { if u == "" { return fmt.Errorf("empty url, index: %d", index) } // todo check the liveness of these URLs, many services do not support } return nil } func validateKafka(brokers []string, topic string) error { if len(brokers) == 0 { return fmt.Errorf("kafka brokers is empty") } if topic == "" { return fmt.Errorf("kafka topic is empty") } for _, addr := range brokers { if addr == "" { return fmt.Errorf("empty kafka broker") } } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() kafkaTariffClient, err := kgo.NewClient( kgo.SeedBrokers(brokers...), kgo.ConsumeResetOffset(kgo.NewOffset().AtStart()), kgo.DefaultProduceTopic(topic), kgo.ConsumeTopics(topic), ) if err != nil { return err } defer kafkaTariffClient.Close() err = kafkaTariffClient.Ping(ctx) if err != nil { return err } return nil } func validateMail(cfg models.MailClientCfg) error { if cfg.MailAddress == "" { return fmt.Errorf("mail address is empty") } if cfg.ApiUrl == "" { return fmt.Errorf("mail api url is empty") } if cfg.ApiKey == "" { return fmt.Errorf("mail api key is empty") } if cfg.Sender == "" { return fmt.Errorf("mail sender is empty") } client := fiber.AcquireClient() req := client.Get("https://api.smtp.bz/v1/user") req.Set("Authorization", cfg.ApiKey) code, _, _ := req.Bytes() if code != fiber.StatusOK { return fmt.Errorf("invalid smtp code, no auth: %d", code) } return nil } func validateEncryptKeys(e *encrypt.Encrypt) error { codeWord := "ДАЙТЕ Jazz-у!" shifr, err := e.EncryptStr(codeWord) if err != nil { return err } deShifr, err := e.DecryptStr(shifr) if err != nil { return err } if deShifr != codeWord { return fmt.Errorf("invalid encrypt key") } return nil } func validateNotEmpty(config *models.Config) error { if config.ExternalCfg.EncryptCommon.PrivKey == "" { return fmt.Errorf("invalid private encrypt key") } if config.ExternalCfg.EncryptCommon.PubKey == "" { return fmt.Errorf("invalid publice encrypt key") } return nil } func validateTG(notificationBotToken string, notificationRsPayChannel int64, notificationChannel int64) error { if notificationBotToken == "" { return fmt.Errorf("notificationBotToken is empty") } // todo обдумать еще регулярку pattern := `^\d+:.+$` ok, err := regexp.MatchString(pattern, notificationBotToken) if err != nil { return fmt.Errorf("error validating notificationBotToken: %w", err) } if !ok { return fmt.Errorf("invalid notificationBotToken format") } if notificationChannel == 0 { return fmt.Errorf("notificationChannel is not set") } if notificationRsPayChannel == 0 { return fmt.Errorf("notificationRsPayChannel is not set") } return nil } type t struct { ID string `bson:"_id,omitempty"` I int `bson:"i"` } // todo в будущем в монге будут запрещены некоторые операции, надо будет обновлять func validateMongo(cfg mongo.Configuration) error { if cfg.URL == "" { return fmt.Errorf("mongo URL is empty") } if cfg.DatabaseName == "" { return fmt.Errorf("mongo database name is empty") } cfg.DatabaseName = "testDBName" ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() database, err := mongo.Connect(ctx, &mongo.ConnectDeps{ Configuration: &cfg, Timeout: 10 * time.Second, }) if err != nil { return err } defer database.Drop(ctx) testCollection := database.Collection(cfg.DatabaseName) receivedChannel := make(chan string, 10) errorChannel := make(chan error, 1) go func() { defer close(receivedChannel) defer close(errorChannel) for i := 0; i <= 100; i++ { d := t{ ID: primitive.NewObjectID().Hex(), I: i, } _, err = testCollection.InsertOne(ctx, d) if err != nil { errorChannel <- err } receivedChannel <- d.ID } }() timeout := time.After(30 * time.Second) for { select { case err = <-errorChannel: if err != nil { return fmt.Errorf("error document insert: %w", err) } case id := <-receivedChannel: result := t{} err := testCollection.FindOne(ctx, bson.M{"_id": id}).Decode(&result) if err != nil { return fmt.Errorf("mongo error finding document: %v", err) } if id != result.ID { return fmt.Errorf("invalid id received") } if result.I == 100 { return nil } case <-timeout: return fmt.Errorf("timeout") } } }