customer/cmd/validator/main.go
2024-11-28 15:33:30 +03:00

354 lines
9.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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{
Host: os.Getenv("MONGO_HOST"),
Port: os.Getenv("MONGO_PORT"),
User: os.Getenv("MONGO_USER"),
Password: os.Getenv("MONGO_PASSWORD"),
DatabaseName: os.Getenv("MONGO_DB_NAME"),
Auth: os.Getenv("MONGO_AUTH"),
},
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"),
ModuleLogger: os.Getenv("MODULE_LOGGER"),
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.ModuleLogger == "" {
return fmt.Errorf("ModuleLogger is empty")
}
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.Host == "" {
return fmt.Errorf("mongo host is empty")
}
if cfg.Port == "" {
return fmt.Errorf("mongo port is empty")
}
if cfg.DatabaseName == "" {
return fmt.Errorf("mongo database name is empty")
}
if cfg.Auth == "" {
return fmt.Errorf("mongo database auth is empty")
}
if cfg.User == "" {
return fmt.Errorf("mongo database user is empty")
}
if cfg.Password == "" {
return fmt.Errorf("mongo database password 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")
}
}
}