diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..35b8c3b --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,24 @@ +name: Deploy +run-name: ${{ gitea.actor }} build image and push to container registry + +on: + push: + branches: + - 'main' + - 'staging' + +jobs: + CreateImage: + runs-on: [squizstaging] + uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/build-image.yml@v1.1.6-p + with: + runner: squizstaging + secrets: + REGISTRY_USER: ${{ secrets.REGISTRY_USER }} + REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} + DeployService: + runs-on: [squizstaging] + needs: CreateImage + uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/deploy.yml@v1.1.7 + with: + runner: squizstaging diff --git a/.gitea/workflows/lint.yml b/.gitea/workflows/lint.yml index 20e6298..0bd7867 100644 --- a/.gitea/workflows/lint.yml +++ b/.gitea/workflows/lint.yml @@ -9,6 +9,6 @@ on: jobs: Lint: runs-on: [hubstaging] - uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/lint.yml@v1.1.0 + uses: http://gitea.pena/PenaDevops/actions.git/.gitea/workflows/lint.yml@v1.1.2 with: runner: hubstaging diff --git a/answerwc/to_client.go b/answerwc/to_client.go deleted file mode 100644 index c3e9878..0000000 --- a/answerwc/to_client.go +++ /dev/null @@ -1,375 +0,0 @@ -package answerwc - -import ( - "context" - _ "embed" - "encoding/json" - "fmt" - "github.com/themakers/hlog" - "gitea.pena/SQuiz/common/dal" - "gitea.pena/SQuiz/common/model" - "gitea.pena/SQuiz/worker/clients/customer" - "gitea.pena/SQuiz/worker/clients/mailclient" - "gitea.pena/SQuiz/worker/wctools" - - "time" - - "github.com/go-redis/redis/v8" -) - -type DepsSendToClient struct { - Redis *redis.Client - Dal *dal.DAL - MailClient *mailclient.Client - CustomerService customer.CustomerServiceClient -} - -type SendToClient struct { - deps DepsSendToClient - logger hlog.Logger - errChan chan<- error -} - -type PendingTasks struct { - Count int64 - QuizConfig model.QuizConfig -} - -//go:embed mail/to_client.tmpl -var toClientTemplate string - -//go:embed mail/reminder.tmpl -var reminderTemplate string - -func NewSendToClient(deps DepsSendToClient, logger hlog.Logger, errChan chan<- error) *SendToClient { - return &SendToClient{ - deps: deps, - logger: logger, - errChan: errChan, - } -} - -func (w *SendToClient) Start(ctx context.Context) { - answerTicker := time.NewTicker(30 * time.Second) - defer answerTicker.Stop() - - for { - select { - case <-answerTicker.C: - w.processPendingAnswer(ctx) - - case <-ctx.Done(): - w.logger.Module("To client worker terminated") - return - - } - } -} - -func (w *SendToClient) processPendingAnswer(ctx context.Context) { - pendingAnswers, err := w.deps.Redis.Keys(ctx, "answer:*").Result() - if err != nil { - fmt.Println("Error getting keys from redis") - w.errChan <- err - return - } - - fmt.Println("ANS") - - for _, key := range pendingAnswers { - func() { - fmt.Println("ANS1", key) - answerJSON, err := w.deps.Redis.GetDel(ctx, key).Result() - if err == redis.Nil { - return - } else if err != nil { - w.reportError(err, "Error getting and deleting data from redis") - return - } - - defer func() { - if r := recover(); r != nil { - w.reportError(nil, fmt.Sprintf("recovering from panic or error setting redis value %v", r)) - fmt.Println("ANS1ERRR", r) - _ = w.deps.Redis.Set(ctx, key, answerJSON, 0).Err() - } - }() - - var answer model.Answer - err = json.Unmarshal([]byte(answerJSON), &answer) - fmt.Println("ANS2", err) - if err != nil { - w.reportError(err, "Error unmarshal answer") - return - } - - answerContent, err := wctools.ProcessAnswer(answer.Content) - fmt.Println("ANS3", err) - if err != nil { - w.reportError(err, "Error unmarshal answer content") - return - } - - allAnswers, err := w.deps.Dal.WorkerAnsRepo.GetAllAnswersByQuizID(ctx, answer.Session) - fmt.Println("ANS4", err) - if err != nil { - w.reportError(err, "Error getting all answers by quizID") - return - } - - questionsMap, sortedallAnswers, err := w.deps.Dal.QuestionRepo.GetMapQuestions(ctx, allAnswers) - fmt.Println("ANS5", err) - if err != nil { - w.reportError(err, "Error getting questionsMap") - return - } - - if answer.QuizId == 0 { - return - } - - quizConfig, accountId, err := w.deps.Dal.QuizRepo.GetQuizConfig(ctx, answer.QuizId) - fmt.Println("ANS6", err) - if err != nil { - w.reportError(err, "Error getting quiz config") - return - } - - quiz, err := w.deps.Dal.QuizRepo.GetQuizById(ctx, accountId, answer.QuizId) - fmt.Println("ANS60", err, accountId, answer.QuizId) - if err != nil { - w.reportError(err, "Error getting quiz") - return - } - - quizConfig.Mailing.Reply = quiz.Name - - if quizConfig.Mailing.Theme == "" { - quizConfig.Mailing.Theme = quiz.Name - } - - account, privileges, err := w.deps.Dal.AccountRepo.GetAccAndPrivilegeByEmail(ctx, accountId) - fmt.Println("ANS7", err) - if err != nil { - w.reportError(err, "Error getting account and privileges by email") - return - } - - result, err := w.processAnswerWithPrivileges(ctx, quiz.Name, quizConfig, questionsMap, privileges, account, sortedallAnswers, answerContent, answer.CreatedAt, answer.QuizId) - fmt.Println("ANS8", err, result, privileges) - if err != nil { - w.reportError(err, "Error process answer with privileges") - return - } - if !result { - err = w.deps.Redis.Set(ctx, fmt.Sprintf("%s:%s", account.ID, key), answerJSON, 0).Err() - if err != nil { - w.reportError(err, "Error setting redis value") - return - } - } - }() - } -} - -func (w *SendToClient) processAnswerWithPrivileges(ctx context.Context, quizName string, quizConfig model.QuizConfig, - questionsMap map[uint64]string, privileges []model.ShortPrivilege, account model.Account, allAnswers []model.ResultAnswer, - answerContent model.ResultContent, answerTime time.Time, quizID uint64) (bool, error) { - - err := w.notificationCustomer(account, privileges) - fmt.Println("ANS81", err) - if err != nil { - return false, err - } - - if wctools.HasUnlimitedPrivilege(privileges) { - err := w.ProcessMessageToClient(quizConfig, questionsMap, account, allAnswers, answerContent, answerTime, quizID) - if err != nil { - return false, err - } - return true, nil - } - privilege := wctools.HasQuizCntPrivilege(privileges) - if privilege != nil { - err := w.ProcessMessageToClient(quizConfig, questionsMap, account, allAnswers, answerContent, answerTime, quizID) - fmt.Println("PMC", err) - if err != nil { - return true, err - } - - privilege.Amount-- - err = w.deps.Dal.AccountRepo.UpdatePrivilegeAmount(ctx, privilege.ID, privilege.Amount) - if err != nil { - return false, err - } - return true, nil - } else { - w.checkAndSendTaskReminders(ctx, sendTaskRemindersDeps{ - email: account.Email, - theme: quizName, - config: model.QuizConfig{ - Mailing: model.ResultInfo{ - When: "email", - Theme: fmt.Sprintf("не удалось отправить заявку по опросу\"%s\"", quizName), - Reply: "noreply@pena.digital", - ReplName: "Reminder", - }, - }, - }) - return false, nil - } -} - -func (w *SendToClient) recordPendingTasks(ctx context.Context, Email string, quizConfig model.QuizConfig) error { - key := fmt.Sprintf("pending_tasks:%s", Email) - - var pendingTasks PendingTasks - val, err := w.deps.Redis.HGet(ctx, key, "data").Result() - if err == nil { - err := json.Unmarshal([]byte(val), &pendingTasks) - if err != nil { - return err - } - pendingTasks.Count++ - } else { - pendingTasks = PendingTasks{ - Count: 1, - QuizConfig: quizConfig, - } - } - - pendingTasksJSON, err := json.Marshal(pendingTasks) - if err != nil { - return err - } - - err = w.deps.Redis.HSet(ctx, key, "data", string(pendingTasksJSON)).Err() - if err != nil { - return err - } - - return nil -} - -type sendTaskRemindersDeps struct { - email, theme string - config model.QuizConfig -} - -func (w *SendToClient) checkAndSendTaskReminders(ctx context.Context, deps sendTaskRemindersDeps) { - err := w.processReminderToClient(deps.email, deps.config) - fmt.Println("PMC1", err) - if err != nil { - w.reportError(err, "Error sending tasks reminder email") - } -} - -func (w *SendToClient) notificationCustomer(account model.Account, privileges []model.ShortPrivilege) error { - for _, privilege := range privileges { - fmt.Println("NOTIFIC", privilege.PrivilegeID, privilege.Amount, !wctools.IsPrivilegeExpired(privilege)) - if privilege.PrivilegeID == "quizUnlimTime" && !wctools.IsPrivilegeExpired(privilege) { - rawDetail, err := wctools.ToJSON(privilege) - historyData := &customer.History{ - UserID: account.UserID, - Comment: fmt.Sprintf("Привилегия %s просрочена", privilege.PrivilegeID), - Key: "privilege_expired", - RawDetails: rawDetail, - } - - _, err = w.deps.CustomerService.InsertHistory(context.Background(), historyData) - if err != nil { - return err - } - - } - - if privilege.PrivilegeID == "quizCnt" && privilege.Amount == 0 { - rawDetail, err := wctools.ToJSON(privilege) - if err != nil { - return err - } - historyData := &customer.History{ - UserID: account.UserID, - Comment: fmt.Sprintf("У привилегии %s истек amount", privilege.PrivilegeID), - Key: "privilege_expired", - RawDetails: rawDetail, - } - - _, err = w.deps.CustomerService.InsertHistory(context.Background(), historyData) - if err != nil { - return err - } - } - } - - return nil -} - -// сделал экспортируемым для теста -func (w *SendToClient) ProcessMessageToClient(quizConfig model.QuizConfig, questionsMap map[uint64]string, account model.Account, allAnswers []model.ResultAnswer, answerContent model.ResultContent, answerTime time.Time, quizID uint64) error { - theme := quizConfig.Mailing.Theme - quizConfig.Mailing.Theme = quizConfig.Mailing.Reply - - data := mailclient.EmailTemplateData{ - QuizConfig: quizConfig.Mailing, - AnswerContent: answerContent, - AllAnswers: allAnswers, - QuestionsMap: questionsMap, - QuizID: quizID, - } - - dayOfWeek := wctools.DaysOfWeek[answerTime.Format("Monday")] - monthOfYear := wctools.MonthsOfYear[answerTime.Format("January")] - - formattedTime := fmt.Sprintf("%s, %d %s %d г., %02d:%02d (UTC%s)", - dayOfWeek, - answerTime.Day(), - monthOfYear, - answerTime.Year(), - answerTime.Hour(), - answerTime.Minute(), - answerTime.Format("-07:00"), - ) - - data.AnswerTime = formattedTime - - fmt.Println("SUBJECT", theme, account.Email) - - err := w.deps.MailClient.SendMailWithAttachment(account.Email, theme, toClientTemplate, data, nil) - if err != nil { - return err - } - - return nil -} - -func (w *SendToClient) processReminderToClient(email string, quizConfig model.QuizConfig) error { - - data := mailclient.EmailTemplateData{ - QuizConfig: model.ResultInfo{ - When: quizConfig.Mailing.When, - Theme: quizConfig.Mailing.Theme, - Reply: email, - ReplName: quizConfig.Mailing.ReplName, - }, - AnswerContent: model.ResultContent{}, - AllAnswers: []model.ResultAnswer{}, - QuestionsMap: nil, - } - - fmt.Println("PRTC", data, email, quizConfig) - - err := w.deps.MailClient.SendMailWithAttachment(email, quizConfig.Mailing.Theme, reminderTemplate, data, nil) - if err != nil { - return err - } - - return nil -} - -func (w *SendToClient) reportError(err error, message string) { - if err != nil { - fmt.Println(message + ": " + err.Error()) - w.errChan <- err - } -} diff --git a/app/app.go b/app/app.go deleted file mode 100644 index b209c05..0000000 --- a/app/app.go +++ /dev/null @@ -1,217 +0,0 @@ -package app - -import ( - "context" - "errors" - "fmt" - "github.com/go-redis/redis/v8" - "github.com/minio/minio-go/v7" - "github.com/minio/minio-go/v7/pkg/credentials" - "github.com/skeris/appInit" - "github.com/themakers/hlog" - "go.uber.org/zap" - "google.golang.org/grpc" - "gitea.pena/SQuiz/common/dal" - "gitea.pena/SQuiz/common/model" - "gitea.pena/SQuiz/worker/answerwc" - "gitea.pena/SQuiz/worker/clients/customer" - "gitea.pena/SQuiz/worker/clients/mailclient" - "gitea.pena/SQuiz/worker/privilegewc" - "gitea.pena/SQuiz/worker/workers/shortstat" - "gitea.pena/SQuiz/worker/workers/timeout" - "time" -) - -type App struct { - logger *zap.Logger - err chan error -} - -func (a App) GetLogger() *zap.Logger { - return a.logger -} - -func (a App) GetErr() chan error { - return a.err -} - -var ( - errInvalidOptions = errors.New("invalid options") -) - -var zapOptions = []zap.Option{ - zap.AddCaller(), - zap.AddCallerSkip(2), - zap.AddStacktrace(zap.ErrorLevel), -} - -var _ appInit.CommonApp = (*App)(nil) - -type Options struct { - ServiceName string `env:"SERVICE_NAME" default:"squiz"` - KafkaBroker string `env:"KAFKA_BROKER"` - KafkaTopic string `env:"KAFKA_TOPIC"` - PrivilegeID string `env:"QUIZ_ID"` - Amount uint64 `env:"AMOUNT"` - UnlimID string `env:"UNLIM_ID"` - LoggerProdMode bool `env:"IS_PROD_LOG" default:"false"` - IsProd bool `env:"IS_PROD" default:"false"` - MinioEP string `env:"MINIO_EP" default:"localhost:3002"` - MinioAK string `env:"MINIO_AK" default:"minio"` - MinioSK string `env:"MINIO_SK" default:"miniostorage"` - PostgresCredentials string `env:"PG_CRED" default:"host=localhost port=5432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"` - RedisHost string `env:"REDIS_HOST" default:"localhost:6379"` - RedisPassword string `env:"REDIS_PASSWORD"` - RedisDB uint64 `env:"REDIS_DB" default:"2"` - SmtpHost string `env:"SMTP_HOST" default:"connect.mailclient.bz"` - SmtpPort string `env:"SMTP_PORT" default:"587"` - SmtpSender string `env:"SMTP_SENDER" default:"noreply@mailing.pena.digital"` - SmtpUsername string `env:"SMTP_USERNAME" default:"kotilion.95@gmail.com"` - SmtpPassword string `env:"SMTP_PASSWORD" default:"vWwbCSg4bf0p"` - SmtpApiKey string `env:"SMTP_API_KEY" default:"P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev"` - SmtpApiUrl string `env:"SMTP_API_URL" default:"https://api.smtp.bz/v1/smtp/send"` - CustomerServiceAddress string `env:"CUSTOMER_SERVICE_ADDRESS"` -} - -func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.CommonApp, error) { - var ( - err, workerErr error - zapLogger *zap.Logger - errChan = make(chan error) - options Options - ok bool - ) - - if options, ok = opts.(Options); !ok { - return App{}, errInvalidOptions - } - - if options.LoggerProdMode { - zapLogger, err = zap.NewProduction(zapOptions...) - if err != nil { - return nil, err - } - } else { - zapLogger, err = zap.NewDevelopment(zapOptions...) - if err != nil { - return nil, err - } - } - - zapLogger = zapLogger.With( - zap.String("SvcCommit", ver.Commit), - zap.String("SvcVersion", ver.Release), - zap.String("SvcBuildTime", ver.BuildTime), - ) - - logger := hlog.New(zapLogger) - logger.Emit(InfoSvcStarted{}) - zapLogger.Info("config", zap.Any("options", options)) - - go func() { - for { - select { - case <-ctx.Done(): - return - case err := <-errChan: - zapLogger.Error("Ошибка при работе воркера", zap.Error(err)) - } - } - }() - - //init redis - redisClient := redis.NewClient(&redis.Options{ - Addr: options.RedisHost, - Password: options.RedisPassword, - DB: int(options.RedisDB), - }) - - smtpData := mailclient.ClientDeps{ - Host: options.SmtpHost, - Port: options.SmtpPort, - Sender: options.SmtpSender, - ApiKey: options.SmtpApiKey, - SmtpApiUrl: options.SmtpApiUrl, - } - - mailClient := mailclient.NewClient(smtpData) - - customerServiceConn, err := grpc.Dial(options.CustomerServiceAddress, grpc.WithInsecure()) - if err != nil { - return nil, err - } - customerServiceClient := customer.NewCustomerServiceClient(customerServiceConn) - - minioClient, err := minio.New(options.MinioEP, &minio.Options{ - Creds: credentials.NewStaticV4(options.MinioAK, options.MinioSK, ""), - Secure: options.IsProd, - }) - if err != nil { - fmt.Println("MINIOERR", options.MinioEP, err) - return nil, err - } - - pgdal, err := dal.New(ctx, options.PostgresCredentials, minioClient) - if err != nil { - return nil, err - } - - kafkaWorker, err := privilegewc.NewKafkaConsumerWorker(privilegewc.Config{ - KafkaBroker: options.KafkaBroker, - KafkaTopic: options.KafkaTopic, - ServiceKey: options.ServiceName, - TickerInterval: time.Second * 10, - Logger: logger, - ErrChan: errChan, - }, redisClient, pgdal) - if err != nil { - logger.Module("Failed start privilege worker") - return nil, err - } - - checkWorker := privilegewc.NewCheckWorker(privilegewc.CheckWorkerConfig{ - DefaultData: model.DefaultData{ - PrivilegeID: options.PrivilegeID, - Amount: options.Amount, - UnlimID: options.UnlimID, - }, - TickerInterval: time.Minute, - Logger: logger, - ErrChan: errChan, - }, pgdal) - - go kafkaWorker.Start(ctx) - go checkWorker.Start(ctx) - toClientWorker := answerwc.NewSendToClient(answerwc.DepsSendToClient{ - Redis: redisClient, - Dal: pgdal, - MailClient: mailClient, - CustomerService: customerServiceClient, - }, logger, errChan) - - toRespWorker := answerwc.NewRespWorker(answerwc.DepsRespWorker{ - Redis: redisClient, - Dal: pgdal, - MailClient: mailClient, - }, logger, errChan) - - go toClientWorker.Start(ctx) - go toRespWorker.Start(ctx) - - tow := timeout.New(pgdal, time.Minute) - statW := shortstat.New(pgdal, 5*time.Minute) - tow.ExposeErr(ctx, &workerErr) - statW.ExposeErr(ctx, &workerErr) - go tow.Start(ctx) - go func() { - // defer pgdal.CloseWorker() - statW.Start(ctx) - }() - - logger.Emit(InfoSvcReady{}) - // todo implement helper func for service app type. such as server preparing, logger preparing, healthchecks and etc. - return &App{ - logger: zapLogger, - err: make(chan error), - }, err -} diff --git a/clients/customer/service.pb.go b/clients/customer/service.pb.go deleted file mode 100644 index 510635d..0000000 --- a/clients/customer/service.pb.go +++ /dev/null @@ -1,182 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.31.0 -// protoc (unknown) -// source: customer/service.proto - -package customer - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - emptypb "google.golang.org/protobuf/types/known/emptypb" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type History struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - UserID string `protobuf:"bytes,1,opt,name=UserID,proto3" json:"UserID,omitempty"` - Comment string `protobuf:"bytes,2,opt,name=Comment,proto3" json:"Comment,omitempty"` - Key string `protobuf:"bytes,3,opt,name=Key,proto3" json:"Key,omitempty"` - RawDetails string `protobuf:"bytes,4,opt,name=RawDetails,proto3" json:"RawDetails,omitempty"` -} - -func (x *History) Reset() { - *x = History{} - if protoimpl.UnsafeEnabled { - mi := &file_customer_service_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *History) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*History) ProtoMessage() {} - -func (x *History) ProtoReflect() protoreflect.Message { - mi := &file_customer_service_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use History.ProtoReflect.Descriptor instead. -func (*History) Descriptor() ([]byte, []int) { - return file_customer_service_proto_rawDescGZIP(), []int{0} -} - -func (x *History) GetUserID() string { - if x != nil { - return x.UserID - } - return "" -} - -func (x *History) GetComment() string { - if x != nil { - return x.Comment - } - return "" -} - -func (x *History) GetKey() string { - if x != nil { - return x.Key - } - return "" -} - -func (x *History) GetRawDetails() string { - if x != nil { - return x.RawDetails - } - return "" -} - -var File_customer_service_proto protoreflect.FileDescriptor - -var file_customer_service_proto_rawDesc = []byte{ - 0x0a, 0x16, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, - 0x65, 0x72, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, - 0x6d, 0x0a, 0x07, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, - 0x4b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x4b, 0x65, 0x79, 0x12, 0x1e, - 0x0a, 0x0a, 0x52, 0x61, 0x77, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x52, 0x61, 0x77, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x32, 0x4f, - 0x0a, 0x0f, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x3c, 0x0a, 0x0d, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, - 0x72, 0x79, 0x12, 0x11, 0x2e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x2e, 0x48, 0x69, - 0x73, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x42, - 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_customer_service_proto_rawDescOnce sync.Once - file_customer_service_proto_rawDescData = file_customer_service_proto_rawDesc -) - -func file_customer_service_proto_rawDescGZIP() []byte { - file_customer_service_proto_rawDescOnce.Do(func() { - file_customer_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_customer_service_proto_rawDescData) - }) - return file_customer_service_proto_rawDescData -} - -var file_customer_service_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_customer_service_proto_goTypes = []interface{}{ - (*History)(nil), // 0: customer.History - (*emptypb.Empty)(nil), // 1: google.protobuf.Empty -} -var file_customer_service_proto_depIdxs = []int32{ - 0, // 0: customer.CustomerService.InsertHistory:input_type -> customer.History - 1, // 1: customer.CustomerService.InsertHistory:output_type -> google.protobuf.Empty - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_customer_service_proto_init() } -func file_customer_service_proto_init() { - if File_customer_service_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_customer_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*History); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_customer_service_proto_rawDesc, - NumEnums: 0, - NumMessages: 1, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_customer_service_proto_goTypes, - DependencyIndexes: file_customer_service_proto_depIdxs, - MessageInfos: file_customer_service_proto_msgTypes, - }.Build() - File_customer_service_proto = out.File - file_customer_service_proto_rawDesc = nil - file_customer_service_proto_goTypes = nil - file_customer_service_proto_depIdxs = nil -} diff --git a/clients/customer/service_grpc.pb.go b/clients/customer/service_grpc.pb.go deleted file mode 100644 index 74454d3..0000000 --- a/clients/customer/service_grpc.pb.go +++ /dev/null @@ -1,108 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.3.0 -// - protoc (unknown) -// source: customer/service.proto - -package customer - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - emptypb "google.golang.org/protobuf/types/known/emptypb" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -const ( - CustomerService_InsertHistory_FullMethodName = "/customer.CustomerService/InsertHistory" -) - -// CustomerServiceClient is the client API for CustomerService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type CustomerServiceClient interface { - InsertHistory(ctx context.Context, in *History, opts ...grpc.CallOption) (*emptypb.Empty, error) -} - -type customerServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewCustomerServiceClient(cc grpc.ClientConnInterface) CustomerServiceClient { - return &customerServiceClient{cc} -} - -func (c *customerServiceClient) InsertHistory(ctx context.Context, in *History, opts ...grpc.CallOption) (*emptypb.Empty, error) { - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, CustomerService_InsertHistory_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// CustomerServiceServer is the server API for CustomerService service. -// All implementations should embed UnimplementedCustomerServiceServer -// for forward compatibility -type CustomerServiceServer interface { - InsertHistory(context.Context, *History) (*emptypb.Empty, error) -} - -// UnimplementedCustomerServiceServer should be embedded to have forward compatible implementations. -type UnimplementedCustomerServiceServer struct { -} - -func (UnimplementedCustomerServiceServer) InsertHistory(context.Context, *History) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method InsertHistory not implemented") -} - -// UnsafeCustomerServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to CustomerServiceServer will -// result in compilation errors. -type UnsafeCustomerServiceServer interface { - mustEmbedUnimplementedCustomerServiceServer() -} - -func RegisterCustomerServiceServer(s grpc.ServiceRegistrar, srv CustomerServiceServer) { - s.RegisterService(&CustomerService_ServiceDesc, srv) -} - -func _CustomerService_InsertHistory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(History) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CustomerServiceServer).InsertHistory(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: CustomerService_InsertHistory_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CustomerServiceServer).InsertHistory(ctx, req.(*History)) - } - return interceptor(ctx, in, info, handler) -} - -// CustomerService_ServiceDesc is the grpc.ServiceDesc for CustomerService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var CustomerService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "customer.CustomerService", - HandlerType: (*CustomerServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "InsertHistory", - Handler: _CustomerService_InsertHistory_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "customer/service.proto", -} diff --git a/clients/mailclient/client.go b/clients/mailclient/client.go deleted file mode 100644 index d766949..0000000 --- a/clients/mailclient/client.go +++ /dev/null @@ -1,133 +0,0 @@ -package mailclient - -import ( - "bytes" - _ "embed" - "fmt" - "github.com/gofiber/fiber/v2" - "html/template" - "mime/multipart" - "gitea.pena/SQuiz/common/model" - "strings" -) - -type ClientDeps struct { - Host string - Port string - Sender string - ApiKey string - SmtpApiUrl string - FiberClient *fiber.Client -} - -type Client struct { - deps ClientDeps -} - -type EmailTemplateData struct { - QuizConfig model.ResultInfo - QuizID uint64 - AnswerContent model.ResultContent - AllAnswers []model.ResultAnswer - QuestionsMap map[uint64]string - AnswerTime string -} - -func NewClient(deps ClientDeps) *Client { - if deps.FiberClient == nil { - deps.FiberClient = fiber.AcquireClient() - } - return &Client{ - deps: deps, - } -} - -func (c *Client) SendMailWithAttachment(recipient, subject string, emailTemplate string, data EmailTemplateData, attachments []Attachment) error { - sanitizedData := sanitizeHTMLData(data) - - text, err := generateTextFromTemplate(sanitizedData, emailTemplate) - if err != nil { - return err - } - - msg := &Message{ - From: c.deps.Sender, - To: []string{recipient}, - Subject: subject, - Body: text, - Attachments: attachments, - } - - return c.Send(msg) -} - -func (c *Client) Send(msg *Message) error { - form := new(bytes.Buffer) - writer := multipart.NewWriter(form) - defer writer.Close() - - fields := map[string]string{ - "from": msg.From, - "to": strings.Join(msg.To, ","), - "subject": msg.Subject, - "html": msg.Body, - } - for key, value := range fields { - if err := writer.WriteField(key, value); err != nil { - return err - } - } - - // пока не используется так как Attachments из воркеров = nil, нужен ли будет потом - for _, attachment := range msg.Attachments { - part, err := writer.CreateFormFile("attachments", attachment.Name) - if err != nil { - return err - } - _, err = part.Write(attachment.Data) - if err != nil { - return err - } - } - - if err := writer.Close(); err != nil { - return err - } - - agent := c.deps.FiberClient.Post(c.deps.SmtpApiUrl).Body(form.Bytes()).ContentType(writer.FormDataContentType()) - if c.deps.ApiKey != "" { - agent.Set("Authorization", c.deps.ApiKey) - } - - statusCode, body, errs := agent.Bytes() - if errs != nil { - return errs[0] - } - - if statusCode != fiber.StatusOK { - return fmt.Errorf("SMTP service returned error: %d, Response body: %s", statusCode, body) - } - - return nil -} - -func generateTextFromTemplate(data EmailTemplateData, tpl string) (string, error) { - t, err := template.New("email").Funcs(tmplFuncs).Parse(tpl) - if err != nil { - return "", fmt.Errorf("error parsing template: %w", err) - } - - var text bytes.Buffer - if err := t.Execute(&text, EmailTemplateData{ - QuizConfig: data.QuizConfig, - AnswerContent: data.AnswerContent, - AllAnswers: data.AllAnswers, - QuestionsMap: data.QuestionsMap, - AnswerTime: data.AnswerTime, - QuizID: data.QuizID, - }); err != nil { - return "", fmt.Errorf("error executing template: %w", err) - } - - return text.String(), nil -} diff --git a/clients/mailclient/message.go b/clients/mailclient/message.go deleted file mode 100644 index 06261ca..0000000 --- a/clients/mailclient/message.go +++ /dev/null @@ -1,104 +0,0 @@ -package mailclient - -import ( - "bytes" - "encoding/base64" - "fmt" - "net/http" - "os" - "path/filepath" - "strings" -) - -type Message struct { - To []string - From string - Subject string - Body string - Attachments []Attachment -} - -type Attachment struct { - Name string - Data []byte -} - -func NewMessage(subject, body string) *Message { - if subject == "" { - subject = "Вам пришла заявка с PenaQuiz" - } - return &Message{Subject: subject, Body: body, Attachments: []Attachment{}} -} - -func (m *Message) AttachFile(src string) error { - data, err := os.ReadFile(src) - if err != nil { - return err - } - - _, filename := filepath.Split(src) - m.Attachments = append(m.Attachments, Attachment{Name: filename, Data: data}) - - return nil -} - -func (m *Message) AttachBytesFile(filename string, data []byte) { - m.Attachments = append(m.Attachments, Attachment{Name: filename, Data: data}) -} - -func (m *Message) ToBytes() []byte { - buf := bytes.NewBuffer(nil) - - buf.WriteString("MIME-Version: 1.0\r\n") - fmt.Fprintf(buf, "From: %s\r\n", m.From) - fmt.Fprintf(buf, "Subject: %s\r\n", m.Subject) - fmt.Fprintf(buf, "To: %s\r\n", strings.Join(m.To, ",")) - - boundary := randomBoundary() - - if len(m.Attachments) > 0 { - buf.WriteString("Content-Type: multipart/mixed;\r\n") - - fmt.Fprintf(buf, " boundary=\"%s\"\r\n", boundary) - - fmt.Fprintf(buf, "\r\n--%s", boundary) - for _, attachment := range m.Attachments { - buf.WriteString("\r\n") - switch strings.Split(attachment.Name, ".")[1] { - case "htmlmsg": - - buf.WriteString("Content-Type: text/html; charset=\"utf-8\"\r\n") - buf.WriteString("Content-Transfer-Encoding: base64\r\n") - case "docx": - - buf.WriteString("Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document\r\n") - buf.WriteString("Content-Transfer-Encoding: base64\r\n") - fmt.Fprintf(buf, "Content-Disposition: attachment; filename=\"%s\"\r\n", attachment.Name) - default: - fmt.Fprintf(buf, "Content-Type: %s\r\n", http.DetectContentType(attachment.Data)) - buf.WriteString("Content-Transfer-Encoding: base64\r\n") - fmt.Fprintf(buf, "Content-Disposition: attachment; filename=\"%s\"\r\n", attachment.Name) - } - - buf.WriteString("\r\n") - - b := make([]byte, base64.StdEncoding.EncodedLen(len(attachment.Data))) - base64.StdEncoding.Encode(b, attachment.Data) - - writer := NewLineWriter(buf, 76) - _, err := writer.Write(b) - if err != nil { - fmt.Println("mailclient-client err:", err) - } - - fmt.Fprintf(buf, "\r\n\r\n--%s", boundary) - } - - buf.WriteString("--") - } else { - buf.WriteString("Content-Type: text/plain; charset=utf-8\r\n") - buf.WriteString(m.Body) - } - - return buf.Bytes() -} diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..9fd0b40 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "context" + "go.uber.org/zap" + "log" + "os" + "os/signal" + "gitea.pena/SQuiz/worker/internal/app" + "gitea.pena/SQuiz/worker/internal/initialize" + "syscall" +) + +var ( + commit string = os.Getenv("COMMIT") + buildTime string = os.Getenv("BUILD_TIME") + version string = os.Getenv("VERSION") +) + +func main() { + config, err := initialize.LoadConfig() + if err != nil { + log.Fatal("Failed to load config", zap.Error(err)) + } + + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + if err = app.New(ctx, *config, app.Build{ + Commit: commit, + Version: version, + }); err != nil { + log.Fatal("App exited with error", zap.Error(err)) + } +} diff --git a/cmd/validator/main.go b/cmd/validator/main.go new file mode 100644 index 0000000..8bce47b --- /dev/null +++ b/cmd/validator/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "context" + "errors" + "gitea.pena/PenaSide/common/validate" + "gitea.pena/SQuiz/common/dal" + "gitea.pena/SQuiz/worker/internal/initialize" + "github.com/caarlos0/env/v8" + "log" +) + +func main() { + cfg, err := loadConfig() + if err != nil { + log.Fatalf("error loading config: %v", err) + } + + err = validateNotEmpty(cfg) + if err != nil { + log.Fatalf("error validating config: %v", err) + } + + err = validate.ValidateKafka([]string{cfg.KafkaBrokers}, cfg.KafkaTopicTariff) + if err != nil { + log.Fatalf("error validating kafka: %v", err) + } + + err = validate.ValidateRedis(cfg.RedisHost, cfg.RedisPassword, int(cfg.RedisDB)) + if err != nil { + log.Fatalf("error validating redis: %v", err) + } + + err = validate.ValidateSmtp(cfg.ApiKey) + if err != nil { + log.Fatalf("error validating smtp: %v", err) + } + + _, err = dal.New(context.TODO(), cfg.PostgresURL, nil) + if err != nil { + log.Fatalf("error connecting to postgres: %v", err) + } + + // s3 пока не валидируем пока не слились все ветки +} + +func loadConfig() (initialize.Config, error) { + var cfg initialize.Config + + if err := env.Parse(&cfg); err != nil { + return cfg, err + } + return cfg, nil +} + +func validateNotEmpty(cfg initialize.Config) error { + if cfg.ServiceName == "" { + return errors.New("service name is not be empty") + } + + if cfg.CustomerMicroserviceRPCURL == "" { + return errors.New("customer microservice rpc url is not be empty") + } + + if cfg.TelegramToken == "" { + return errors.New("telegram token is not be empty") + } + + if cfg.Sender == "" { + return errors.New("sender is not be empty") + } + + if cfg.ApiKey == "" { + return errors.New("api key is not be empty") + } + + if cfg.ApiUrl == "" { + return errors.New("api url is not be empty") + } + + return nil +} diff --git a/deployments/staging/docker-compose.yaml b/deployments/staging/docker-compose.yaml index 32fdb85..5dadec8 100644 --- a/deployments/staging/docker-compose.yaml +++ b/deployments/staging/docker-compose.yaml @@ -1,20 +1,19 @@ -version: "3" services: workerv1.0.0: hostname: squiz-workerv1.0.0 container_name: squiz-workerv1.0.0 - image: $CI_REGISTRY_IMAGE/staging-worker:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID + image: gitea.pena/squiz/worker/staging:$GITHUB_RUN_NUMBER tty: true environment: IS_PROD_LOG: 'false' IS_PROD: 'false' - PG_CRED: 'host=10.8.0.5 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable' - KAFKA_BROKER: '10.8.0.6:9092' + PG_CRED: 'host=10.7.0.10 port=5432 user=squiz password=Redalert2 dbname=squiz sslmode=disable' + KAFKA_BROKER: '10.7.0.6:9092' KAFKA_TOPIC: 'tariffs' QUIZ_ID: quizCnt AMOUNT: 10 UNLIM_ID: quizUnlimTime - REDIS_HOST: '10.8.0.5:6379' + REDIS_HOST: '10.7.0.10:6379' REDIS_PASSWORD: 'Redalert2' REDIS_DB: 2 SMTP_HOST: 'connect.mailclient.bz' @@ -24,7 +23,7 @@ services: SMTP_USERNAME: 'kotilion.95@gmail.com' SMTP_PASSWORD: 'vWwbCSg4bf0p' SMTP_API_KEY: 'P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev' - CUSTOMER_SERVICE_ADDRESS: '10.8.0.6:9066' + CUSTOMER_SERVICE_ADDRESS: '10.7.0.6:9060' MINIO_EP: s3.timeweb.cloud MINIO_AK: 5CV77KVDUU9H0II9R24M MINIO_SK: 0W0m8DyvdAKRJnsAy6mB5zndQ7RouJBLhqhtThcu diff --git a/deployments/test/docker-compose.yaml b/deployments/test/docker-compose.yaml index b66713c..2e6731e 100644 --- a/deployments/test/docker-compose.yaml +++ b/deployments/test/docker-compose.yaml @@ -18,50 +18,50 @@ services: timeout: 2s retries: 10 -# need update! -# test-pena-auth-service: -# image: penahub.gitlab.yandexcloud.net:5050/pena-services/pena-auth-service:staging.872 -# container_name: test-pena-auth-service -# init: true -# env_file: auth.env.test -# healthcheck: -# test: wget -T1 --spider http://localhost:8000/user -# interval: 2s -# timeout: 2s -# retries: 5 -# environment: -# - DB_HOST=test-pena-auth-db -# - DB_PORT=27017 -# - ENVIRONMENT=staging -# - HTTP_HOST=0.0.0.0 -# - HTTP_PORT=8000 -# - DB_USERNAME=test -# - DB_PASSWORD=test -# - DB_NAME=admin -# - DB_AUTH=admin -# # ports: -# # - 8000:8000 -# depends_on: -# - test-pena-auth-db -# # - pena-auth-migration -# networks: -# - penatest -# -# test-pena-auth-db: -# container_name: test-pena-auth-db -# init: true -# image: "mongo:6.0.3" -# command: mongod --quiet --logpath /dev/null -# volumes: -# - test-mongodb:/data/db -# - test-mongoconfdb:/data/configdb -# environment: -# MONGO_INITDB_ROOT_USERNAME: test -# MONGO_INITDB_ROOT_PASSWORD: test -# # ports: -# # - 27017:27017 -# networks: -# - penatest + # need update! + # test-pena-auth-service: + # image: penahub.gitlab.yandexcloud.net:5050/pena-services/pena-auth-service:staging.872 + # container_name: test-pena-auth-service + # init: true + # env_file: auth.env.test + # healthcheck: + # test: wget -T1 --spider http://localhost:8000/user + # interval: 2s + # timeout: 2s + # retries: 5 + # environment: + # - DB_HOST=test-pena-auth-db + # - DB_PORT=27017 + # - ENVIRONMENT=staging + # - HTTP_HOST=0.0.0.0 + # - HTTP_PORT=8000 + # - DB_USERNAME=test + # - DB_PASSWORD=test + # - DB_NAME=admin + # - DB_AUTH=admin + # # ports: + # # - 8000:8000 + # depends_on: + # - test-pena-auth-db + # # - pena-auth-migration + # networks: + # - penatest + # + # test-pena-auth-db: + # container_name: test-pena-auth-db + # init: true + # image: "mongo:6.0.3" + # command: mongod --quiet --logpath /dev/null + # volumes: + # - test-mongodb:/data/db + # - test-mongoconfdb:/data/configdb + # environment: + # MONGO_INITDB_ROOT_USERNAME: test + # MONGO_INITDB_ROOT_PASSWORD: test + # # ports: + # # - 27017:27017 + # networks: + # - penatest test-minio: container_name: test-minio @@ -82,8 +82,8 @@ services: depends_on: test-postgres: condition: service_healthy -# test-pena-auth-service: -# condition: service_healthy + # test-pena-auth-service: + # condition: service_healthy # volumes: # - ./../..:/app:ro # command: [ "go", "test", "./tests", "-run", "TestFoo" ] diff --git a/go.mod b/go.mod index d0ba77c..33e9577 100644 --- a/go.mod +++ b/go.mod @@ -5,60 +5,69 @@ go 1.23.2 toolchain go1.23.4 require ( - gitea.pena/SQuiz/common v0.0.0-20250207214652-9994f2d4d43f + gitea.pena/PenaSide/common v0.0.0-20250103085335-91ea31fee517 + gitea.pena/PenaSide/customer v0.0.0-20250407185330-82e95e8da043 + gitea.pena/PenaSide/hlog v0.0.0-20241125221102-a54c29c002a9 + gitea.pena/SQuiz/common v0.0.0-20250514124515-870e52266ca5 + github.com/caarlos0/env/v8 v8.0.0 github.com/go-redis/redis/v8 v8.11.5 + github.com/go-resty/resty/v2 v2.16.5 github.com/gofiber/fiber/v2 v2.52.4 github.com/golang/protobuf v1.5.4 + github.com/google/uuid v1.6.0 + github.com/joho/godotenv v1.5.1 github.com/minio/minio-go/v7 v7.0.81 github.com/pioz/faker v1.7.3 - github.com/skeris/appInit v1.0.2 github.com/stretchr/testify v1.9.0 - github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf github.com/twmb/franz-go v1.18.0 go.uber.org/zap v1.27.0 - golang.org/x/net v0.30.0 - google.golang.org/grpc v1.64.0 - google.golang.org/protobuf v1.34.1 + golang.org/x/net v0.35.0 + gopkg.in/tucnak/telebot.v2 v2.5.0 ) require ( - github.com/andybalholm/brotli v1.1.0 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/dustin/go-humanize v1.0.1 // indirect ) require ( - gitea.pena/PenaSide/common v0.0.0-20250103085335-91ea31fee517 // indirect github.com/ClickHouse/clickhouse-go v1.5.4 // indirect github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/goccy/go-json v0.10.3 // indirect - github.com/google/uuid v1.6.0 // indirect + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/klauspost/compress v1.17.11 // indirect - github.com/klauspost/cpuid/v2 v2.2.8 // indirect - github.com/kr/pretty v0.3.1 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/lib/pq v1.10.9 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/minio/md5-simd v1.1.2 // indirect + github.com/montanaflynn/stats v0.7.1 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/rs/xid v1.6.0 // indirect github.com/twmb/franz-go/pkg/kmsg v1.9.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.54.0 // indirect - github.com/valyala/tcplisten v1.0.0 // indirect + github.com/valyala/fasthttp v1.59.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect + go.mongodb.org/mongo-driver v1.14.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.28.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7648035..72e11a4 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,23 @@ gitea.pena/PenaSide/common v0.0.0-20250103085335-91ea31fee517 h1:EgBe8VcdPwmxbSzYLndncP+NmR73uYuXxkTeDlEttEE= gitea.pena/PenaSide/common v0.0.0-20250103085335-91ea31fee517/go.mod h1:91EuBCgcqgJ6mG36n2pds8sPwwfaJytLWOzY3h2YFKU= -gitea.pena/SQuiz/common v0.0.0-20250207214652-9994f2d4d43f h1:458FCN98jVkjAqg3yyspgkUdJnKz3BNMiZosrVtPpv8= -gitea.pena/SQuiz/common v0.0.0-20250207214652-9994f2d4d43f/go.mod h1:/YR+uo4RouZshuHPkguk7nAJVKuFt3Z0mTFxUPdlzxQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +gitea.pena/PenaSide/customer v0.0.0-20250407185330-82e95e8da043 h1:a/89TC0bRdWRjS875EIE1c1ZPgmRcDaKMcQoUv+sg+I= +gitea.pena/PenaSide/customer v0.0.0-20250407185330-82e95e8da043/go.mod h1:DDg6CQDhU+aSSTv8R/LbkNGegdCx+Q++DcgPfE2LbZU= +gitea.pena/PenaSide/hlog v0.0.0-20241125221102-a54c29c002a9 h1:tBkXWNIt8icmkMMnq8MA421RWkUy4OZh5P7C3q8uCu4= +gitea.pena/PenaSide/hlog v0.0.0-20241125221102-a54c29c002a9/go.mod h1:sanhSL8aEsfcq21P+eItYiAnKAre+B67nGJmDfk2cf0= +gitea.pena/SQuiz/common v0.0.0-20250514124515-870e52266ca5 h1:C+iCsGMSUJonOTNNk8wWYOfzZ0Jjw+2IQ5FaEGwRVT0= +gitea.pena/SQuiz/common v0.0.0-20250514124515-870e52266ca5/go.mod h1:zCrUwDh0APpztKk6NUqTZv+zhjVbWpGBJiJ5z9dAH0U= github.com/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0= github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk= github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= +github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0= +github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -27,6 +31,8 @@ github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= +github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= @@ -34,41 +40,40 @@ github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84Egg github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= -github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.81 h1:SzhMN0TQ6T/xSBu6Nvw3M5M8voM+Ht8RH3hE8S7zxaA= github.com/minio/minio-go/v7 v7.0.81/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= @@ -81,93 +86,106 @@ github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pioz/faker v1.7.3 h1:Tez8Emuq0UN+/d6mo3a9m/9ZZ/zdfJk0c5RtRatrceM= github.com/pioz/faker v1.7.3/go.mod h1:xSpay5w/oz1a6+ww0M3vfpe40pSIykeUPeWEc3TvVlc= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/skeris/appInit v1.0.2 h1:Hr4KbXYd6kolTVq4cXGqDpgnpmaauiOiKizA1+Ep4KQ= -github.com/skeris/appInit v1.0.2/go.mod h1:4ElEeXWVGzU3dlYq/eMWJ/U5hd+LKisc1z3+ySh1XmY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf h1:TJJm6KcBssmbWzplF5lzixXl1RBAi/ViPs1GaSOkhwo= -github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf/go.mod h1:1FsorU3vnXO9xS9SrhUp8fRb/6H/Zfll0rPt1i4GWaA= github.com/twmb/franz-go v1.18.0 h1:25FjMZfdozBywVX+5xrWC2W+W76i0xykKjTdEeD2ejw= github.com/twmb/franz-go v1.18.0/go.mod h1:zXCGy74M0p5FbXsLeASdyvfLFsBvTubVqctIaa5wQ+I= github.com/twmb/franz-go/pkg/kmsg v1.9.0 h1:JojYUph2TKAau6SBtErXpXGC7E3gg4vGZMv9xFU/B6M= github.com/twmb/franz-go/pkg/kmsg v1.9.0/go.mod h1:CMbfazviCyY6HM0SXuG5t9vOwYDHRCSrJJyBAe5paqg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.54.0 h1:cCL+ZZR3z3HPLMVfEYVUMtJqVaui0+gu7Lx63unHwS0= -github.com/valyala/fasthttp v1.54.0/go.mod h1:6dt4/8olwq9QARP/TDuPmWyWcl4byhpvTJ4AAtcz+QM= -github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= -github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI= +github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= +go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/tucnak/telebot.v2 v2.5.0 h1:i+NynLo443Vp+Zn3Gv9JBjh3Z/PaiKAQwcnhNI7y6Po= +gopkg.in/tucnak/telebot.v2 v2.5.0/go.mod h1:BgaIIx50PSRS9pG59JH+geT82cfvoJU/IaI5TJdN3v8= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/answerwc/respondent.go b/internal/answerwc/respondent.go similarity index 91% rename from answerwc/respondent.go rename to internal/answerwc/respondent.go index af0b4e5..2c34b89 100644 --- a/answerwc/respondent.go +++ b/internal/answerwc/respondent.go @@ -4,31 +4,28 @@ import ( "context" "encoding/json" "fmt" - "github.com/go-redis/redis/v8" - "github.com/themakers/hlog" "gitea.pena/SQuiz/common/dal" "gitea.pena/SQuiz/common/model" - "gitea.pena/SQuiz/worker/clients/mailclient" - "gitea.pena/SQuiz/worker/wctools" + senders2 "gitea.pena/SQuiz/worker/internal/senders" + "gitea.pena/SQuiz/worker/internal/wctools" + "github.com/go-redis/redis/v8" "time" ) type DepsRespWorker struct { Redis *redis.Client Dal *dal.DAL - MailClient *mailclient.Client + MailClient *senders2.MailLeadSender } type RespWorker struct { deps DepsRespWorker - logger hlog.Logger errChan chan<- error } -func NewRespWorker(deps DepsRespWorker, logger hlog.Logger, errChan chan<- error) *RespWorker { +func NewRespWorker(deps DepsRespWorker, errChan chan<- error) *RespWorker { return &RespWorker{ deps: deps, - logger: logger, errChan: errChan, } } @@ -43,7 +40,6 @@ func (w *RespWorker) Start(ctx context.Context) { w.processPendingAnswer(ctx) case <-ctx.Done(): - w.logger.Module("To respondent worker terminated") return } @@ -132,7 +128,7 @@ func (w *RespWorker) processMessageToSMTP(quizConfig model.QuizConfig, questions theme := quizConfig.Mailing.Theme quizConfig.Mailing.Theme = quizConfig.Mailing.Reply - data := mailclient.EmailTemplateData{ + data := senders2.TemplateData{ QuizConfig: quizConfig.Mailing, AnswerContent: answerContent, AllAnswers: allAnswers, diff --git a/internal/answerwc/template/client_tg.tmpl b/internal/answerwc/template/client_tg.tmpl new file mode 100644 index 0000000..5c9a4fa --- /dev/null +++ b/internal/answerwc/template/client_tg.tmpl @@ -0,0 +1,26 @@ +*Квиз для вашего бизнеса* + +*Поступила новая заявка с квиза "{{.QuizConfig.Theme}}"!* +Время заявки: {{ .AnswerTime }} + +Имя: {{ .AnswerContent.Name }} +{{ if .AnswerContent.Email }}Email: {{ .AnswerContent.Email }}{{ end }} +{{ if .AnswerContent.Phone }}Телефон: {{ .AnswerContent.Phone }}{{ end }} +{{ if .AnswerContent.Telegram }}Telegram: {{ .AnswerContent.Telegram }}{{ end }} +{{ if .AnswerContent.Wechat }}Wechat: {{ .AnswerContent.Wechat }}{{ end }} +{{ if .AnswerContent.Viber }}Viber: {{ .AnswerContent.Viber }}{{ end }} +{{ if .AnswerContent.Vk }}Vk: {{ .AnswerContent.Vk }}{{ end }} +{{ if .AnswerContent.Skype }}Skype: {{ .AnswerContent.Skype }}{{ end }} +{{ if .AnswerContent.Whatsup }}Whatsup: {{ .AnswerContent.Whatsup }}{{ end }} +{{ if .AnswerContent.Messenger }}Messenger: {{ .AnswerContent.Messenger }}{{ end }} +{{ if .AnswerContent.Address }}Адрес: {{ .AnswerContent.Address }}{{ end }} +{{ range $key, $value := .AnswerContent.Custom }}{{ $key }}: {{ $value }}{{ end }} + +*Ответы:* +{{ range .AllAnswers }} + {{ if index $.QuestionsMap .AnswerID }} + *{{ index $.QuestionsMap .AnswerID }}* + {{ .Content }} + {{ end }} +{{ end }} + diff --git a/internal/answerwc/template/client_whatsapp.tmpl b/internal/answerwc/template/client_whatsapp.tmpl new file mode 100644 index 0000000..e69de29 diff --git a/answerwc/mail/reminder.tmpl b/internal/answerwc/template/reminder.tmpl similarity index 100% rename from answerwc/mail/reminder.tmpl rename to internal/answerwc/template/reminder.tmpl diff --git a/internal/answerwc/template/reminder_tg.tmpl b/internal/answerwc/template/reminder_tg.tmpl new file mode 100644 index 0000000..92cac31 --- /dev/null +++ b/internal/answerwc/template/reminder_tg.tmpl @@ -0,0 +1,9 @@ +# Поступила новая заявка с квиза “{{ .QuizConfig.Theme }}”! +Но у вас закончились средства на балансе :( +![Wallet](https://storage.yandexcloud.net/squizimages/img_wallet.png) +## Аккаунт +**Email:** {{ .QuizConfig.Reply }} +Пополните баланс и посмотрите заявку в личном кабинете: +[Посмотреть в личном кабинете](https://quiz.pena.digital) +--- +[quiz.pena.digital](https://quiz.pena.digital) diff --git a/internal/answerwc/template/reminder_whatsapp.tmpl b/internal/answerwc/template/reminder_whatsapp.tmpl new file mode 100644 index 0000000..e69de29 diff --git a/answerwc/mail/to_client.tmpl b/internal/answerwc/template/to_client.tmpl similarity index 100% rename from answerwc/mail/to_client.tmpl rename to internal/answerwc/template/to_client.tmpl diff --git a/internal/answerwc/to_client.go b/internal/answerwc/to_client.go new file mode 100644 index 0000000..a7a9951 --- /dev/null +++ b/internal/answerwc/to_client.go @@ -0,0 +1,491 @@ +package answerwc + +import ( + "context" + _ "embed" + "encoding/json" + "errors" + "fmt" + "gitea.pena/PenaSide/customer/pkg/customer_clients" + "gitea.pena/SQuiz/common/dal" + "gitea.pena/SQuiz/common/model" + "gitea.pena/SQuiz/common/pj_errors" + "gitea.pena/SQuiz/worker/internal/senders" + "gitea.pena/SQuiz/worker/internal/wctools" + "github.com/go-redis/redis/v8" + "time" +) + +type DepsSendToClient struct { + Redis *redis.Client + Dal *dal.DAL + LeadSenders []senders.LeadSender + CustomerService *customer_clients.CustomersClient +} + +type SendToClient struct { + redis *redis.Client + dal *dal.DAL + leadSenders []senders.LeadSender + customerService *customer_clients.CustomersClient + errChan chan<- error + toClientTemplates map[model.LeadTargetType]string + toReminderTemplates map[model.LeadTargetType]string +} + +type PendingTasks struct { + Count int64 + QuizConfig model.QuizConfig +} + +//go:embed template/to_client.tmpl +var toClientTemplate string + +//go:embed template/reminder.tmpl +var reminderTemplate string + +//go:embed template/reminder_tg.tmpl +var reminderTgTemplate string + +//go:embed template/reminder_whatsapp.tmpl +var reminderWhatsAppTemplate string + +//go:embed template/client_tg.tmpl +var toClientTgTemplate string + +//go:embed template/client_whatsapp.tmpl +var toClientWhatsAppTemplate string + +func NewSendToClient(deps DepsSendToClient, errChan chan<- error) *SendToClient { + toClientTemplates := map[model.LeadTargetType]string{ + model.LeadTargetEmail: toClientTemplate, + model.LeadTargetTg: toClientTgTemplate, + model.LeadTargetWhatsapp: toClientWhatsAppTemplate, + } + + toReminderTemplates := map[model.LeadTargetType]string{ + model.LeadTargetEmail: reminderTemplate, + model.LeadTargetTg: reminderTgTemplate, + model.LeadTargetWhatsapp: reminderWhatsAppTemplate, + } + + return &SendToClient{ + redis: deps.Redis, + dal: deps.Dal, + customerService: deps.CustomerService, + errChan: errChan, + leadSenders: deps.LeadSenders, + toClientTemplates: toClientTemplates, + toReminderTemplates: toReminderTemplates, + } +} + +func (w *SendToClient) Start(ctx context.Context) { + answerTicker := time.NewTicker(30 * time.Second) + defer answerTicker.Stop() + + for { + select { + case <-answerTicker.C: + w.processPendingAnswer(ctx) + + case <-ctx.Done(): + return + + } + } +} + +func (w *SendToClient) processPendingAnswer(ctx context.Context) { + pendingAnswers, err := w.redis.Keys(ctx, "answer:*").Result() + if err != nil { + fmt.Println("Error getting keys from redis") + w.errChan <- err + return + } + + fmt.Println("ANS") + + for _, key := range pendingAnswers { + func() { + fmt.Println("ANS1", key) + answerJSON, err := w.redis.GetDel(ctx, key).Result() + if err == redis.Nil { + return + } else if err != nil { + w.reportError(err, "Error getting and deleting data from redis") + return + } + + defer func() { + if r := recover(); r != nil { + w.reportError(nil, fmt.Sprintf("recovering from panic or error setting redis value %v", r)) + fmt.Println("ANS1ERRR", r) + _ = w.redis.Set(ctx, key, answerJSON, 0).Err() + } + }() + + var answer model.Answer + err = json.Unmarshal([]byte(answerJSON), &answer) + fmt.Println("ANS2", err) + if err != nil { + w.reportError(err, "Error unmarshal answer") + return + } + + answerContent, err := wctools.ProcessAnswer(answer.Content) + fmt.Println("ANS3", err) + if err != nil { + w.reportError(err, "Error unmarshal answer content") + return + } + + allAnswersDirty, err := w.dal.AnswerRepo.GetAllAnswersByQuizID(ctx, answer.Session) + fmt.Println("ANS4", err) + if err != nil { + w.reportError(err, "Error getting all answers by quizID") + return + } + allAnswers := wctools.CleanNullContent(allAnswersDirty) + + questionsMap, sortedallAnswers, err := w.dal.QuestionRepo.GetMapQuestions(ctx, allAnswers) + fmt.Println("ANS5", err) + if err != nil { + w.reportError(err, "Error getting questionsMap") + return + } + + if answer.QuizId == 0 { + return + } + + quizConfig, accountId, err := w.dal.QuizRepo.GetQuizConfig(ctx, answer.QuizId) + fmt.Println("ANS6", err) + if err != nil { + w.reportError(err, "Error getting quiz config") + return + } + + quiz, err := w.dal.QuizRepo.GetQuizById(ctx, accountId, answer.QuizId) + fmt.Println("ANS60", err, accountId, answer.QuizId) + if err != nil { + w.reportError(err, "Error getting quiz") + return + } + + quizConfig.Mailing.Reply = quiz.Name + + if quizConfig.Mailing.Theme == "" { + quizConfig.Mailing.Theme = quiz.Name + } + + account, privileges, err := w.dal.AccountRepo.GetAccAndPrivilegeByEmail(ctx, accountId) + fmt.Println("ANS7", err) + if err != nil { + w.reportError(err, "Error getting account and privileges by email") + return + } + + result, err := w.processAnswerWithPrivileges(ctx, ProcessAnsWithPriv{ + quiz: quiz, + quizConfig: quizConfig, + questionsMap: questionsMap, + privileges: privileges, + account: account, + allAnswers: sortedallAnswers, + answerContent: answerContent, + answerTime: answer.CreatedAt, + }) + fmt.Println("ANS8", err, result, privileges) + if err != nil { + w.reportError(err, "Error process answer with privileges") + return + } + if !result { + err = w.redis.Set(ctx, fmt.Sprintf("%s:%s", account.ID, key), answerJSON, 0).Err() + if err != nil { + w.reportError(err, "Error setting redis value") + return + } + } + }() + } +} + +type ProcessAnsWithPriv struct { + quiz *model.Quiz + quizConfig model.QuizConfig + questionsMap map[uint64]string + privileges []model.ShortPrivilege + account model.Account + allAnswers []model.ResultAnswer + answerContent model.ResultContent + answerTime time.Time +} + +func (w *SendToClient) processAnswerWithPrivileges(ctx context.Context, data ProcessAnsWithPriv) (bool, error) { + + err := w.notificationCustomer(ctx, data.account, data.privileges) + fmt.Println("ANS81", err) + if err != nil { + return false, err + } + + if wctools.HasUnlimitedPrivilege(data.privileges) { + err := w.ProcessMessageToClient(ctx, DepsProcessMsgToClient{ + QuizConfig: data.quizConfig, + QuestionsMap: data.questionsMap, + Account: data.account, + AllAnswers: data.allAnswers, + AnswerContent: data.answerContent, + AnswerTime: data.answerTime, + Quiz: data.quiz, + }) + if err != nil { + return false, err + } + return true, nil + } + privilege := wctools.HasQuizCntPrivilege(data.privileges) + if privilege != nil { + err := w.ProcessMessageToClient(ctx, DepsProcessMsgToClient{ + QuizConfig: data.quizConfig, + QuestionsMap: data.questionsMap, + Account: data.account, + AllAnswers: data.allAnswers, + AnswerContent: data.answerContent, + AnswerTime: data.answerTime, + Quiz: data.quiz, + }) + fmt.Println("PMC", err) + if err != nil { + return true, err + } + + privilege.Amount-- + err = w.dal.AccountRepo.UpdatePrivilegeAmount(ctx, privilege.ID, privilege.Amount) + if err != nil { + return false, err + } + return true, nil + } else { + w.checkAndSendTaskReminders(ctx, sendTaskRemindersDeps{ + quiz: data.quiz, + account: data.account, + theme: data.quiz.Name, + config: model.QuizConfig{ + Mailing: model.ResultInfo{ + When: "email", + Theme: fmt.Sprintf("не удалось отправить заявку по опросу\"%s\"", data.quiz.Name), + Reply: "noreply@pena.digital", + ReplName: "Reminder", + }, + }, + }) + return false, nil + } +} + +func (w *SendToClient) recordPendingTasks(ctx context.Context, Email string, quizConfig model.QuizConfig) error { + key := fmt.Sprintf("pending_tasks:%s", Email) + + var pendingTasks PendingTasks + val, err := w.redis.HGet(ctx, key, "data").Result() + if err == nil { + err := json.Unmarshal([]byte(val), &pendingTasks) + if err != nil { + return err + } + pendingTasks.Count++ + } else { + pendingTasks = PendingTasks{ + Count: 1, + QuizConfig: quizConfig, + } + } + + pendingTasksJSON, err := json.Marshal(pendingTasks) + if err != nil { + return err + } + + err = w.redis.HSet(ctx, key, "data", string(pendingTasksJSON)).Err() + if err != nil { + return err + } + + return nil +} + +type sendTaskRemindersDeps struct { + account model.Account + theme string + config model.QuizConfig + quiz *model.Quiz +} + +func (w *SendToClient) checkAndSendTaskReminders(ctx context.Context, data sendTaskRemindersDeps) { + err := w.ProcessReminderToClient(ctx, data.account, data.config, data.quiz) + fmt.Println("PMC1", err) + if err != nil { + w.reportError(err, "Error sending tasks reminder email") + } +} + +func (w *SendToClient) notificationCustomer(ctx context.Context, account model.Account, privileges []model.ShortPrivilege) error { + for _, privilege := range privileges { + fmt.Println("NOTIFIC", privilege.PrivilegeID, privilege.Amount, !wctools.IsPrivilegeExpired(privilege)) + if privilege.PrivilegeID == "quizUnlimTime" && !wctools.IsPrivilegeExpired(privilege) { + historyData := customer_clients.InsertHistoryDeps{ + UserID: account.UserID, + Comment: fmt.Sprintf("%s privilege has expired, it was created at %d", privilege.PrivilegeID, privilege.CreatedAt.Unix()), + Key: "privilege_expired", + } + + err := w.customerService.InsertHistory(ctx, historyData) + if err != nil { + return err + } + + } + + if privilege.PrivilegeID == "quizCnt" && privilege.Amount == 0 { + historyData := customer_clients.InsertHistoryDeps{ + UserID: account.UserID, + Comment: fmt.Sprintf("%s privilege has expired, it was created at %d", privilege.PrivilegeID, privilege.CreatedAt.Unix()), + Key: "privilege_expired", + } + + err := w.customerService.InsertHistory(ctx, historyData) + if err != nil { + return err + } + } + } + + return nil +} + +type DepsProcessMsgToClient struct { + QuizConfig model.QuizConfig + QuestionsMap map[uint64]string + Account model.Account + AllAnswers []model.ResultAnswer + AnswerContent model.ResultContent + AnswerTime time.Time + Quiz *model.Quiz +} + +// сделал экспортируемым для теста +func (w *SendToClient) ProcessMessageToClient(ctx context.Context, constructData DepsProcessMsgToClient) error { + leadTargetForAll, err := w.dal.AccountRepo.GetLeadTarget(ctx, constructData.Quiz.AccountId, 0) + if err != nil && !errors.Is(err, pj_errors.ErrNotFound) { + return err + } + leadTargetForQuiz, err := w.dal.AccountRepo.GetLeadTarget(ctx, constructData.Quiz.AccountId, int32(constructData.Quiz.Id)) + if err != nil && !errors.Is(err, pj_errors.ErrNotFound) { + return err + } + if len(leadTargetForQuiz) > 0 { + leadTargetForAll = append(leadTargetForAll, leadTargetForQuiz...) + } + + theme := constructData.QuizConfig.Mailing.Theme + constructData.QuizConfig.Mailing.Theme = constructData.QuizConfig.Mailing.Reply + + data := senders.TemplateData{ + QuizConfig: constructData.QuizConfig.Mailing, + AnswerContent: constructData.AnswerContent, + AllAnswers: constructData.AllAnswers, + QuestionsMap: constructData.QuestionsMap, + } + + dayOfWeek := wctools.DaysOfWeek[constructData.AnswerTime.Format("Monday")] + monthOfYear := wctools.MonthsOfYear[constructData.AnswerTime.Format("January")] + + formattedTime := fmt.Sprintf("%s, %d %s %d г., %02d:%02d (UTC%s)", + dayOfWeek, + constructData.AnswerTime.Day(), + monthOfYear, + constructData.AnswerTime.Year(), + constructData.AnswerTime.Hour(), + constructData.AnswerTime.Minute(), + constructData.AnswerTime.Format("-07:00"), + ) + + data.AnswerTime = formattedTime + mapLeadTarget := make(map[string][]senders.LeadData) // ключ имя сендера, модель отправки + for _, leadTarget := range leadTargetForAll { + mapLeadTarget[string(leadTarget.Type)] = append(mapLeadTarget[string(leadTarget.Type)], senders.LeadData{ + To: leadTarget.Target, + Subject: theme, + TemplateData: data, + Template: w.toClientTemplates[leadTarget.Type], + }) + } + + for _, sender := range w.leadSenders { + for _, sendData := range mapLeadTarget[sender.Name()] { + err := sender.SendLead(sendData) + if err != nil { + w.reportError(err, fmt.Sprintf("Error sending lead through %s", sender.Name())) + } + } + } + + return nil +} + +func (w *SendToClient) ProcessReminderToClient(ctx context.Context, account model.Account, quizConfig model.QuizConfig, quiz *model.Quiz) error { + leadTargetForAll, err := w.dal.AccountRepo.GetLeadTarget(ctx, account.UserID, 0) + if err != nil && !errors.Is(err, pj_errors.ErrNotFound) { + return err + } + + leadTargetForQuiz, err := w.dal.AccountRepo.GetLeadTarget(ctx, account.UserID, int32(quiz.Id)) + if err != nil && !errors.Is(err, pj_errors.ErrNotFound) { + return err + } + if len(leadTargetForQuiz) > 0 { + leadTargetForAll = append(leadTargetForAll, leadTargetForQuiz...) + } + mapLeadTarget := make(map[string][]senders.LeadData) + for _, leadTarget := range leadTargetForAll { + data := senders.TemplateData{ + QuizConfig: model.ResultInfo{ + When: quizConfig.Mailing.When, + Theme: quizConfig.Mailing.Theme, + Reply: leadTarget.Target, + ReplName: quizConfig.Mailing.ReplName, + }, + AnswerContent: model.ResultContent{}, + AllAnswers: []model.ResultAnswer{}, + QuestionsMap: nil, + } + + fmt.Println("PRTC", data, leadTarget.Target, quizConfig) + mapLeadTarget[string(leadTarget.Type)] = append(mapLeadTarget[string(leadTarget.Type)], senders.LeadData{ + To: leadTarget.Target, + Subject: quizConfig.Mailing.Theme, + Template: w.toReminderTemplates[leadTarget.Type], + TemplateData: data, + }) + } + for _, sender := range w.leadSenders { + for _, sendData := range mapLeadTarget[sender.Name()] { + err := sender.SendLead(sendData) + if err != nil { + w.reportError(err, fmt.Sprintf("Error sending lead through %s", sender.Name())) + } + } + } + + return nil +} + +func (w *SendToClient) reportError(err error, message string) { + if err != nil { + fmt.Println(message + ": " + err.Error()) + w.errChan <- err + } +} diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..04c0069 --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,196 @@ +package app + +import ( + "context" + "crypto/tls" + "errors" + "gitea.pena/PenaSide/hlog" + "gitea.pena/SQuiz/common/dal" + "gitea.pena/SQuiz/worker/internal/answerwc" + "gitea.pena/SQuiz/worker/internal/clients/gigachat" + "gitea.pena/SQuiz/worker/internal/gigachatwc" + "gitea.pena/SQuiz/worker/internal/initialize" + "gitea.pena/SQuiz/worker/internal/privilegewc" + "gitea.pena/SQuiz/worker/internal/senders" + "gitea.pena/SQuiz/worker/internal/workers/shortstat" + "gitea.pena/SQuiz/worker/internal/workers/timeout" + "gitea.pena/SQuiz/worker/pkg/closer" + "github.com/go-resty/resty/v2" + "go.uber.org/zap" + "time" +) + +var zapOptions = []zap.Option{ + zap.AddCaller(), + zap.AddCallerSkip(2), + zap.AddStacktrace(zap.ErrorLevel), +} + +type Build struct { + Commit string + Version string +} + +func New(ctx context.Context, cfg initialize.Config, build Build) error { + var ( + err, workerErr error + zapLogger *zap.Logger + errChan = make(chan error) + ) + + if cfg.LoggerProdMode { + zapLogger, err = zap.NewProduction(zapOptions...) + if err != nil { + return err + } + } else { + zapLogger, err = zap.NewDevelopment(zapOptions...) + if err != nil { + return err + } + } + + zapLogger = zapLogger.With( + zap.String("SvcCommit", build.Commit), + zap.String("SvcVersion", build.Version), + zap.String("SvcBuildTime", time.Now().String()), + ) + + logger := hlog.New(zapLogger) + logger.Emit(InfoSvcStarted{}) + + shutdownGroup := closer.NewCloserGroup() + + go func() { + for { + select { + case <-ctx.Done(): + return + case err := <-errChan: + zapLogger.Error("Ошибка при работе воркера", zap.Error(err)) + } + } + }() + + redisClient, err := initialize.Redis(ctx, cfg) + if err != nil { + zapLogger.Error("failed init redis", zap.Error(err)) + return err + } + + minioClient, err := initialize.NewMinio(cfg) + if err != nil { + zapLogger.Error("failed init minio", zap.Error(err)) + return err + } + + clients := initialize.NewClients(cfg, zapLogger) + + // tgSender, err := senders.NewTgSender(options.TgToken) + // if err != nil { + // fmt.Println(err) + // return nil, err + // } + mailSender := senders.NewMailLeadSender(clients.MailClient) + leadSenders := []senders.LeadSender{mailSender /* , tgSender */} + + pgdal, err := dal.New(ctx, cfg.PostgresURL, minioClient) + if err != nil { + zapLogger.Error("failed init postgres", zap.Error(err)) + return err + } + + kafkaWorker, err := privilegewc.NewKafkaConsumerWorker(privilegewc.Config{ + KafkaBroker: cfg.KafkaBrokers, + KafkaTopic: cfg.KafkaTopicTariff, + ServiceKey: cfg.ServiceName, + TickerInterval: time.Second * 10, + ErrChan: errChan, + }, redisClient, pgdal) + if err != nil { + zapLogger.Error("Failed start privilege worker", zap.Error(err)) + return err + } + + checkWorker := privilegewc.NewCheckWorker(privilegewc.Deps{ + PrivilegeIDsDays: []string{"quizUnlimTime", "squizHideBadge"}, + PrivilegeIDsCount: []string{"quizCnt", "quizManual"}, + TickerInterval: time.Minute, + PrivilegeDAL: pgdal, + CustomerClient: clients.CustomerClient, + }, errChan) + + go kafkaWorker.Start(ctx) + go checkWorker.Start(ctx) + + toClientWorker := answerwc.NewSendToClient(answerwc.DepsSendToClient{ + Redis: redisClient, + Dal: pgdal, + LeadSenders: leadSenders, + CustomerService: clients.CustomerClient, + }, errChan) + + toRespWorker := answerwc.NewRespWorker(answerwc.DepsRespWorker{ + Redis: redisClient, + Dal: pgdal, + MailClient: mailSender, + }, errChan) + + go toClientWorker.Start(ctx) + go toRespWorker.Start(ctx) + + gigaChatClient, err := gigachat.NewGigaChatClient(ctx, gigachat.Deps{ + Logger: zapLogger, + Client: resty.New().SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}), + BaseURL: cfg.GigaChatApiBaseURL, + AuthKey: cfg.GigaChatApiAuthKey, + RedisClient: redisClient, + }) + if err != nil { + zapLogger.Error("failed init giga chat client", zap.Error(err)) + return err + } + // метод для обновления токенов гигачата + go gigaChatClient.TokenResearch(ctx) + + gigaChatWorker, err := gigachatwc.NewGigaChatTaskScheduler(gigachatwc.Deps{ + KafkaBrokers: cfg.KafkaBrokers, + KafkaTopic: cfg.KafkaTopicGigaChat, + KafkaGroup: cfg.KafkaGroupGigaChat, + GigaChatClient: gigaChatClient, + Logger: zapLogger, + Dal: pgdal, + }) + if err != nil { + zapLogger.Error("failed init giga chat task worker", zap.Error(err)) + return err + } + + go gigaChatWorker.Start(ctx) + + tow := timeout.New(pgdal, time.Minute) + statW := shortstat.New(pgdal, 5*time.Minute) + tow.ExposeErr(ctx, &workerErr) + statW.ExposeErr(ctx, &workerErr) + go tow.Start(ctx) + go statW.Start(ctx) + + logger.Emit(InfoSvcReady{}) + shutdownGroup.Add(closer.CloserFunc(pgdal.Close)) + + <-ctx.Done() + + timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), 10*time.Second) + defer timeoutCancel() + if err := shutdownGroup.Call(timeoutCtx); err != nil { + if errors.Is(err, context.DeadlineExceeded) { + zapLogger.Error("Shutdown timed out", zap.Error(err)) + } else { + zapLogger.Error("Failed to shutdown services gracefully", zap.Error(err)) + } + return err + } + + zapLogger.Info("Application has stopped") + return nil +} diff --git a/app/logrecords.go b/internal/app/logrecords.go similarity index 100% rename from app/logrecords.go rename to internal/app/logrecords.go diff --git a/internal/clients/gigachat/client.go b/internal/clients/gigachat/client.go new file mode 100644 index 0000000..166dd55 --- /dev/null +++ b/internal/clients/gigachat/client.go @@ -0,0 +1,149 @@ +package gigachat + +import ( + "context" + "errors" + "fmt" + "gitea.pena/SQuiz/common/model" + "github.com/go-redis/redis/v8" + "github.com/go-resty/resty/v2" + "github.com/google/uuid" + "go.uber.org/zap" + "time" +) + +type Deps struct { + Logger *zap.Logger + Client *resty.Client + BaseURL string + AuthKey string + RedisClient *redis.Client +} + +type GigaChatClient struct { + logger *zap.Logger + client *resty.Client + baseURL string + authKey string + redisClient *redis.Client +} + +func NewGigaChatClient(ctx context.Context, deps Deps) (*GigaChatClient, error) { + client := &GigaChatClient{ + logger: deps.Logger, + client: deps.Client, + baseURL: deps.BaseURL, + authKey: deps.AuthKey, + redisClient: deps.RedisClient, + } + + if err := client.updateToken(ctx); err != nil { + return nil, fmt.Errorf("failed to get access token: %w", err) + } + + return client, nil +} + +func (r *GigaChatClient) SendMsg(ctx context.Context, audience model.GigaChatAudience, question model.Question) (string, error) { + gender := "женский" + if audience.Sex { + gender = "мужской" + } + userInput := fmt.Sprintf(model.ReworkQuestionPrompt, audience.Age, gender, question.Title, question.Description) + + token, err := r.redisClient.Get(ctx, "gigachat_token").Result() + if err != nil { + r.logger.Error("failed to get token from redis", zap.Error(err)) + return "", err + } + + reqBody := model.GigaChatRequest{ + Model: "GigaChat-2-Max", + Stream: false, + UpdateInterval: 0, + Messages: []model.GigaChatMessage{ + {Role: "system", Content: model.CreatePrompt}, + {Role: "user", Content: userInput}, + }, + } + + var response model.GigaChatResponse + + resp, err := r.client.R(). + SetHeader("Content-Type", "application/json"). + SetHeader("Authorization", "Bearer "+token). + SetBody(reqBody). + SetResult(&response). + Post(r.baseURL + "/chat/completions") + + if err != nil { + r.logger.Error("failed send request to GigaChat", zap.Error(err)) + return "", err + } + + if resp.IsError() { + errMsg := fmt.Sprintf("error GigaChat API: %s", resp.Status()) + r.logger.Error(errMsg) + return "", errors.New(errMsg) + } + + if len(response.Choices) == 0 || response.Choices[0].Message.Content == "" { + // когда возникает такая ошибка то значит еще траим отправить запрос + return "", model.EmptyResponseErrorGigaChat + } + + return response.Choices[0].Message.Content, nil +} + +func (r *GigaChatClient) TokenResearch(ctx context.Context) { + ticker := time.NewTicker(5 * time.Minute) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + ttl, err := r.redisClient.TTL(ctx, "gigachat_token").Result() + if err != nil || ttl < 2*time.Minute { + if err := r.updateToken(ctx); err != nil { + r.logger.Error("failed to update GigaChat token", zap.Error(err)) + } else { + r.logger.Info("successfully updated GigaChat token") + } + } + case <-ctx.Done(): + return + } + } +} + +func (r *GigaChatClient) updateToken(ctx context.Context) error { + formData := "scope=GIGACHAT_API_PERS" + + var respData struct { + AccessToken string `json:"access_token"` + ExpiresAt int64 `json:"expires_at"` + } + + resp, err := r.client.R(). + SetHeader("Authorization", "Basic "+r.authKey). + SetHeader("Content-Type", "application/x-www-form-urlencoded"). + SetBody(formData). + SetHeader("RqUID", uuid.New().String()). + SetResult(&respData). + Post("https://ngw.devices.sberbank.ru:9443/api/v2/oauth") + + if err != nil { + return err + } + if resp.IsError() { + return fmt.Errorf("token request failed: %s", resp.Status()) + } + + ttl := time.Until(time.Unix(respData.ExpiresAt, 0)) + err = r.redisClient.Set(ctx, "gigachat_token", respData.AccessToken, ttl).Err() + if err != nil { + return fmt.Errorf("failed to save token to redis: %w", err) + } + + return nil +} diff --git a/internal/gigachatwc/scheduler.go b/internal/gigachatwc/scheduler.go new file mode 100644 index 0000000..6a0c8e2 --- /dev/null +++ b/internal/gigachatwc/scheduler.go @@ -0,0 +1,141 @@ +package gigachatwc + +import ( + "context" + "encoding/json" + "errors" + "gitea.pena/SQuiz/common/dal" + "gitea.pena/SQuiz/common/model" + "gitea.pena/SQuiz/worker/internal/clients/gigachat" + "github.com/twmb/franz-go/pkg/kgo" + "go.uber.org/zap" + "time" +) + +type GigaChatTaskScheduler struct { + kafkaClient *kgo.Client + gigaChatClient *gigachat.GigaChatClient + logger *zap.Logger + dal *dal.DAL +} + +type Deps struct { + KafkaBrokers string + KafkaTopic string + KafkaGroup string + GigaChatClient *gigachat.GigaChatClient + Logger *zap.Logger + Dal *dal.DAL +} + +func NewGigaChatTaskScheduler(deps Deps) (*GigaChatTaskScheduler, error) { + client, err := kgo.NewClient( + kgo.SeedBrokers(deps.KafkaBrokers), + kgo.ConsumerGroup(deps.KafkaGroup), + kgo.ConsumeTopics(deps.KafkaTopic), + kgo.ConsumeResetOffset(kgo.NewOffset().AfterMilli(time.Now().UnixMilli())), + ) + if err != nil { + return nil, err + } + + return &GigaChatTaskScheduler{ + kafkaClient: client, + gigaChatClient: deps.GigaChatClient, + logger: deps.Logger, + dal: deps.Dal, + }, nil +} + +type MessageGigaChat struct { + ID int64 `json:"id"` + QuizID int64 `json:"quiz_id"` + Sex bool `json:"sex"` + Age string `json:"age"` +} + +type UpdJsonQuestionData struct { + Title string `json:"title"` + Description string `json:"description"` +} + +func (r *GigaChatTaskScheduler) Start(ctx context.Context) { + ticker := time.NewTicker(3 * time.Minute) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + r.fetchMessages(ctx) + case <-ctx.Done(): + return + } + } +} + +func (r *GigaChatTaskScheduler) fetchMessages(ctx context.Context) { + fetches := r.kafkaClient.PollFetches(ctx) + iter := fetches.RecordIter() + + for !iter.Done() { + record := iter.Next() + + var msg MessageGigaChat + if err := json.Unmarshal(record.Value, &msg); err != nil { + r.logger.Error("failed to unmarshal kafka message", zap.ByteString("value", record.Value), zap.Error(err)) + continue + } + + r.handleMessage(ctx, msg) + } +} + +func (r *GigaChatTaskScheduler) handleMessage(ctx context.Context, msg MessageGigaChat) { + listQuestions, _, err := r.dal.QuestionRepo.GetQuestionList(ctx, 100_000, 0, 0, 0, uint64(msg.QuizID), false, false, "", "", 0) + if err != nil { + r.logger.Error("failed to get question list", zap.Error(err)) + return + } + + audience := model.GigaChatAudience{ + ID: msg.ID, + QuizID: msg.QuizID, + Sex: msg.Sex, + Age: msg.Age, + } + + for _, question := range listQuestions { + var ( + updJson string + resp UpdJsonQuestionData + ) + // вот дилемма сколько попыток нам нужно? может ли гигачат отлючить нам некоторые ограничения + // можем ли мы скипать вопросы с которыми у нас произошла ошибка + for i := 0; i < 10; i++ { + updJson, err = r.gigaChatClient.SendMsg(ctx, audience, question) + if err == nil { + if err = json.Unmarshal([]byte(updJson), &resp); err == nil { + question.Title = resp.Title + question.Description = resp.Description + break + } + } + + if errors.Is(err, model.EmptyResponseErrorGigaChat) { + r.logger.Warn("empty response from GigaChat, retrying...", zap.Int("attempt", i+1), zap.Uint64("question_id", question.Id)) + continue + } + + r.logger.Error("failed to send message to GigaChat", zap.Error(err)) + break + } + + question.Auditory = msg.ID + + _, err = r.dal.QuestionRepo.CreateQuestion(ctx, &question) + if err != nil { + r.logger.Error("failed to create updated question", zap.Error(err)) + continue + } + } +} diff --git a/internal/initialize/clients.go b/internal/initialize/clients.go new file mode 100644 index 0000000..47bbf31 --- /dev/null +++ b/internal/initialize/clients.go @@ -0,0 +1,26 @@ +package initialize + +import ( + "gitea.pena/PenaSide/customer/pkg/customer_clients" + "gitea.pena/SQuiz/common/clients" + "go.uber.org/zap" +) + +type Clients struct { + MailClient *clients.SmtpClient + CustomerClient *customer_clients.CustomersClient +} + +func NewClients(cfg Config, logger *zap.Logger) *Clients { + return &Clients{ + MailClient: clients.NewSmtpClient(clients.Deps{ + SmtpSender: cfg.Sender, + ApiKey: cfg.ApiKey, + SmtpApiUrl: cfg.ApiUrl, + }), + CustomerClient: customer_clients.NewCustomersClient(customer_clients.CustomersClientDeps{ + Logger: logger, + CustomerServiceHost: cfg.CustomerMicroserviceRPCURL, + }), + } +} diff --git a/internal/initialize/config.go b/internal/initialize/config.go new file mode 100644 index 0000000..3d92d2b --- /dev/null +++ b/internal/initialize/config.go @@ -0,0 +1,43 @@ +package initialize + +import ( + "github.com/caarlos0/env/v8" + "github.com/joho/godotenv" + "log" +) + +type Config struct { + ServiceName string `env:"SERVICE_NAME" envDefault:"squiz"` + KafkaBrokers string `env:"KAFKA_BROKERS" envDefault:"localhost:6379"` + KafkaTopicTariff string `env:"KAFKA_TOPIC_TARIFF" envDefault:"test-topic"` + LoggerProdMode bool `env:"IS_PROD_LOG" envDefault:"false"` + IsProd bool `env:"IS_PROD" envDefault:"false"` + S3Endpoint string `env:"S3_ENDPOINT" envDefault:"localhost:3002"` + S3AccessKey string `env:"S3_ACCESS_KEY" envDefault:"minio"` + S3SecretKey string `env:"S3_SECRET_KEY" envDefault:"miniostorage"` + PostgresURL string `env:"POSTGRES_URL" envDefault:"host=localhost port=5432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"` + RedisHost string `env:"REDIS_HOST"` + RedisPassword string `env:"REDIS_PASSWORD"` + RedisDB uint64 `env:"REDIS_DB"` + Sender string `env:"MAIL_SENDER" envDefault:"noreply@mailing.pena.digital"` + ApiKey string `env:"MAIL_API_KEY" envDefault:"P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev"` + ApiUrl string `env:"API_URL" envDefault:"https://api.smtp.bz/v1/smtp/send"` + CustomerMicroserviceRPCURL string `env:"CUSTOMER_MICROSERVICE_RPC_URL" envDefault:"localhost:9001"` + TelegramToken string `env:"TELEGRAM_TOKEN"` + + KafkaTopicGigaChat string `env:"KAFKA_TOPIC_GIGA_CHAT"` + KafkaGroupGigaChat string `env:"KAFKA_GROUP_GIGA_CHAT" default:"gigachat"` + GigaChatApiBaseURL string `env:"GIGA_CHAT_API_BASE_URL"` + GigaChatApiAuthKey string `env:"GIGA_CHAT_API_AUTH_KEY"` +} + +func LoadConfig() (*Config, error) { + if err := godotenv.Load(); err != nil { + log.Print("No .env file found") + } + var config Config + if err := env.Parse(&config); err != nil { + return nil, err + } + return &config, nil +} diff --git a/internal/initialize/minio.go b/internal/initialize/minio.go new file mode 100644 index 0000000..c5d2a4f --- /dev/null +++ b/internal/initialize/minio.go @@ -0,0 +1,17 @@ +package initialize + +import ( + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" +) + +func NewMinio(cfg Config) (*minio.Client, error) { + minioClient, err := minio.New(cfg.S3Endpoint, &minio.Options{ + Creds: credentials.NewStaticV4(cfg.S3AccessKey, cfg.S3SecretKey, ""), + Secure: cfg.IsProd, + }) + if err != nil { + return nil, err + } + return minioClient, nil +} diff --git a/internal/initialize/redis.go b/internal/initialize/redis.go new file mode 100644 index 0000000..eb14f05 --- /dev/null +++ b/internal/initialize/redis.go @@ -0,0 +1,21 @@ +package initialize + +import ( + "context" + "github.com/go-redis/redis/v8" +) + +func Redis(ctx context.Context, cfg Config) (*redis.Client, error) { + rdb := redis.NewClient(&redis.Options{ + Addr: cfg.RedisHost, + Password: cfg.RedisPassword, + DB: int(cfg.RedisDB), + }) + + status := rdb.Ping(ctx) + if err := status.Err(); err != nil { + return nil, err + } + + return rdb, nil +} diff --git a/internal/privilegewc/check.go b/internal/privilegewc/check.go new file mode 100644 index 0000000..b6ad725 --- /dev/null +++ b/internal/privilegewc/check.go @@ -0,0 +1,96 @@ +package privilegewc + +import ( + "context" + "fmt" + "gitea.pena/PenaSide/customer/pkg/customer_clients" + "gitea.pena/SQuiz/common/dal" + "gitea.pena/SQuiz/common/model" + "time" +) + +type Deps struct { + PrivilegeDAL *dal.DAL + TickerInterval time.Duration + PrivilegeIDsDays []string + PrivilegeIDsCount []string + CustomerClient *customer_clients.CustomersClient +} + +type CheckWorker struct { + privilegeDAL *dal.DAL + tickerInterval time.Duration + errChan chan<- error + privilegeIDsDays []string + privilegeIDsCount []string + customerClient *customer_clients.CustomersClient +} + +func NewCheckWorker(deps Deps, errChan chan<- error) *CheckWorker { + return &CheckWorker{ + privilegeDAL: deps.PrivilegeDAL, + tickerInterval: deps.TickerInterval, + errChan: errChan, + privilegeIDsCount: deps.PrivilegeIDsCount, + privilegeIDsDays: deps.PrivilegeIDsDays, + customerClient: deps.CustomerClient, + } +} + +func (w *CheckWorker) Start(ctx context.Context) { + ticker := time.NewTicker(w.tickerInterval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + fmt.Println("CHECK") + w.deleteExpired(ctx) + case <-ctx.Done(): + fmt.Println("Check worker terminated") + return + } + } +} + +func (w *CheckWorker) deleteExpired(ctx context.Context) { + var toHistory []customer_clients.InsertHistoryDeps + var expiredData []model.ExpiredPrivileges + + for _, id := range w.privilegeIDsDays { + expired, err := w.privilegeDAL.AccountRepo.GetExpired(ctx, id) + if err != nil { + w.errChan <- err + } + expiredData = append(expiredData, expired...) + } + + for _, id := range w.privilegeIDsCount { + expired, err := w.privilegeDAL.AccountRepo.GetExpiredCount(ctx, id) + if err != nil { + w.errChan <- err + } + expiredData = append(expiredData, expired...) + } + + for _, data := range expiredData { + err := w.privilegeDAL.AccountRepo.DeletePrivilegeByID(ctx, data.Privilege.ID) + if err != nil { + w.errChan <- err + continue + } + + toHistory = append(toHistory, customer_clients.InsertHistoryDeps{ + UserID: data.UserID, + Comment: fmt.Sprintf("%s privilege has expired, it was created at %d", data.Privilege.PrivilegeID, data.Privilege.CreatedAt.Unix()), + Key: "privilege_expired", + }) + } + + for _, to := range toHistory { + err := w.customerClient.InsertHistory(ctx, to) + if err != nil { + w.errChan <- err + } + } +} diff --git a/privilegewc/consumer.go b/internal/privilegewc/consumer.go similarity index 97% rename from privilegewc/consumer.go rename to internal/privilegewc/consumer.go index e3702ca..9301973 100644 --- a/privilegewc/consumer.go +++ b/internal/privilegewc/consumer.go @@ -3,12 +3,11 @@ package privilegewc import ( "context" "fmt" - "github.com/go-redis/redis/v8" - "github.com/themakers/hlog" - "github.com/twmb/franz-go/pkg/kgo" "gitea.pena/SQuiz/common/dal" "gitea.pena/SQuiz/common/model" - "gitea.pena/SQuiz/worker/wctools" + "gitea.pena/SQuiz/worker/internal/wctools" + "github.com/go-redis/redis/v8" + "github.com/twmb/franz-go/pkg/kgo" "strings" "time" ) @@ -19,7 +18,6 @@ type Config struct { KafkaTopic string ServiceKey string TickerInterval time.Duration - Logger hlog.Logger ErrChan chan<- error } diff --git a/privilegewc/consumer_test.go b/internal/privilegewc/consumer_test.go similarity index 72% rename from privilegewc/consumer_test.go rename to internal/privilegewc/consumer_test.go index 5bafec0..816652c 100644 --- a/privilegewc/consumer_test.go +++ b/internal/privilegewc/consumer_test.go @@ -2,18 +2,18 @@ package privilegewc import ( "context" - dal2 "gitea.pena/SQuiz/common/dal" + "gitea.pena/SQuiz/common/dal" "gitea.pena/SQuiz/common/model" "testing" ) func TestProcessValidMessage(t *testing.T) { ctx := context.Background() - dal, err := dal2.New(ctx, "host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable", nil) + d, err := dal.New(ctx, "host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable", nil) if err != nil { panic(err) } - cfg, err := NewKafkaConsumerWorker(Config{}, nil, dal) + cfg, err := NewKafkaConsumerWorker(Config{}, nil, d) if err != nil { panic(err) } diff --git a/clients/mailclient/utils.go b/internal/senders/common.go similarity index 71% rename from clients/mailclient/utils.go rename to internal/senders/common.go index 520a377..c779002 100644 --- a/clients/mailclient/utils.go +++ b/internal/senders/common.go @@ -1,71 +1,56 @@ -package mailclient +package senders import ( "bytes" - "crypto/rand" + _ "embed" + "encoding/base64" "encoding/json" "fmt" "golang.org/x/net/html" "html/template" - "io" "gitea.pena/SQuiz/common/model" "strings" ) -type LineWriter struct { - w io.Writer - length int +type LeadSender interface { + SendLead(leadData LeadData) error + Name() string } -func NewLineWriter(w io.Writer, length int) *LineWriter { - return &LineWriter{ - w: w, - length: length, - } +type LeadData struct { + To interface{} + Subject string + Template string + TemplateData TemplateData } -func (r *LineWriter) Write(p []byte) (n int, err error) { - for i := 0; i < len(p); i += r.length { - end := i + r.length - - if end > len(p) { - end = len(p) - 1 - } - - var chunk []byte - chunk = append(chunk, p[i:end]...) - - if len(p) >= end+r.length { - chunk = append(chunk, []byte("\r\n")...) - } - - addN, err := r.w.Write(chunk) - if err != nil { - return n, err - } - n += addN - } - - return n, nil +type TemplateData struct { + QuizConfig model.ResultInfo + AnswerContent model.ResultContent + AllAnswers []model.ResultAnswer + QuestionsMap map[uint64]string + AnswerTime string + QuizID int64 } -func (r *LineWriter) WriteString(s string) (n int, err error) { - p := []byte(s) - return r.Write(p) -} - -func (r *LineWriter) WriteFormatString(format string, a ...any) (n int, err error) { - p := []byte(fmt.Sprintf(format, a...)) - return r.Write(p) -} - -func randomBoundary() string { - var buf [30]byte - _, err := io.ReadFull(rand.Reader, buf[:]) +func generateTextFromTemplate(data TemplateData, tpl string) (string, error) { + t, err := template.New("email").Funcs(tmplFuncs).Parse(tpl) if err != nil { - panic(err) + return "", fmt.Errorf("error parsing template: %w", err) } - return fmt.Sprintf("%x", buf[:]) + + var text bytes.Buffer + if err := t.Execute(&text, TemplateData{ + QuizConfig: data.QuizConfig, + AnswerContent: data.AnswerContent, + AllAnswers: data.AllAnswers, + QuestionsMap: data.QuestionsMap, + AnswerTime: data.AnswerTime, + }); err != nil { + return "", fmt.Errorf("error executing template: %w", err) + } + + return text.String(), nil } var tmplFuncs = template.FuncMap{ @@ -73,7 +58,7 @@ var tmplFuncs = template.FuncMap{ } func RenderImage(content string) template.HTML { - contents := strings.Split(content, "`,`") + contents := strings.Split(content, "`,`") var builder strings.Builder for i, cnt := range contents { @@ -84,11 +69,9 @@ func RenderImage(content string) template.HTML { cnt = strings.TrimSuffix(cnt, "`") } - fmt.Println("RI1", cnt) var res model.ImageContent err := json.Unmarshal([]byte(cnt), &res) if err != nil { - fmt.Println("RI1", content) return SplitContent(content) } else { builder.WriteString(fmt.Sprintf("%s
", res.Description, strings.Replace(res.Image,"http","https",1))) @@ -102,14 +85,21 @@ func SplitContent(content string) template.HTML { parts := strings.Split(content, "|") if len(parts) == 2 { url := strings.TrimSpace(parts[0]) - filename := strings.TrimSpace(parts[1]) - return template.HTML(fmt.Sprintf(`%s`, strings.Replace(url,"http","https",1), filename)) - } - return template.HTML(strings.ReplaceAll(strings.ReplaceAll(content,"`,`","
"),"`","")) + filenameBase64 := strings.TrimSpace(parts[1]) + filenameBytes, err := base64.StdEncoding.DecodeString(filenameBase64) + if err != nil { + return template.HTML(fmt.Sprintf(`%s`, url, "invalid filename")) + } + + filename := string(filenameBytes) + + return template.HTML(fmt.Sprintf(`%s`, url, filename)) + } + return template.HTML(strings.ReplaceAll(content, "\n", "
")) } -func sanitizeHTMLData(data EmailTemplateData) EmailTemplateData { - sanitized := EmailTemplateData{ +func sanitizeHTMLData(data TemplateData) TemplateData { + sanitized := TemplateData{ QuizConfig: stripHTMLResultInfo(data.QuizConfig), AnswerContent: stripHTMLResultContent(data.AnswerContent), AllAnswers: stripHTMLResultAnswers(data.AllAnswers), diff --git a/internal/senders/mail_sender.go b/internal/senders/mail_sender.go new file mode 100644 index 0000000..4e484d3 --- /dev/null +++ b/internal/senders/mail_sender.go @@ -0,0 +1,43 @@ +package senders + +import ( + "gitea.pena/SQuiz/common/clients" +) + +type MailLeadSender struct { + client *clients.SmtpClient +} + +func NewMailLeadSender(client *clients.SmtpClient) *MailLeadSender { + return &MailLeadSender{client: client} +} + +func (m *MailLeadSender) SendLead(data LeadData) error { + err := m.SendMailWithAttachment(data.To.(string), data.Subject, data.Template, data.TemplateData, nil) + if err != nil { + return err + } + + return nil +} + +func (m *MailLeadSender) SendMailWithAttachment(recipient, subject string, emailTemplate string, data TemplateData, attachments []clients.Attachment) error { + sanitizedData := sanitizeHTMLData(data) + text, err := generateTextFromTemplate(sanitizedData, emailTemplate) + if err != nil { + return err + } + + msg := clients.Message{ + To: recipient, + Subject: subject, + HtmlBody: text, + Attachments: attachments, + } + + return m.client.MailSender(msg) +} + +func (m *MailLeadSender) Name() string { + return "mail" +} diff --git a/internal/senders/tg_sender.go b/internal/senders/tg_sender.go new file mode 100644 index 0000000..a2c5a5c --- /dev/null +++ b/internal/senders/tg_sender.go @@ -0,0 +1,51 @@ +package senders + +import ( + "fmt" + "gopkg.in/tucnak/telebot.v2" + "strconv" + "time" +) + +type TgSender struct { + bot *telebot.Bot +} + +func NewTgSender(tgToken string) (*TgSender, error) { + bot, err := telebot.NewBot(telebot.Settings{ + Token: tgToken, + Poller: &telebot.LongPoller{Timeout: 10 * time.Second}, + }) + + if err != nil { + return nil, fmt.Errorf("error creating Telegram bot: %w", err) + } + + return &TgSender{bot: bot}, nil +} + +func (tg *TgSender) SendLead(data LeadData) error { + text, err := generateTextFromTemplate(data.TemplateData, data.Template) + if err != nil { + return err + } + + chatStr := data.To.(string) + + chat, err := strconv.ParseInt(chatStr, 10, 64) + if err != nil { + return err + } + _, err = tg.bot.Send(telebot.ChatID(chat), text, &telebot.SendOptions{ + ParseMode: telebot.ModeHTML, + }) + if err != nil { + return fmt.Errorf("error sending Telegram message: %w", err) + } + + return nil +} + +func (tg *TgSender) Name() string { + return "telegram" +} diff --git a/internal/senders/whatsapp_sender.go b/internal/senders/whatsapp_sender.go new file mode 100644 index 0000000..6ae9eaf --- /dev/null +++ b/internal/senders/whatsapp_sender.go @@ -0,0 +1,19 @@ +package senders + +import "fmt" + +type WhatsAppSender struct { +} + +func NewWhatsAppSender() *WhatsAppSender { + return &WhatsAppSender{} +} + +func (wa *WhatsAppSender) SendLead(data LeadData) error { + fmt.Println("TODO") + return nil +} + +func (wa *WhatsAppSender) Name() string { + return "whatsapp" +} diff --git a/wctools/tools.go b/internal/wctools/tools.go similarity index 90% rename from wctools/tools.go rename to internal/wctools/tools.go index 4096b5e..0dd9686 100644 --- a/wctools/tools.go +++ b/internal/wctools/tools.go @@ -3,12 +3,12 @@ package wctools import ( "encoding/json" "errors" - "github.com/golang/protobuf/proto" + "fmt" "gitea.pena/SQuiz/common/model" "gitea.pena/SQuiz/common/model/tariff" + "github.com/golang/protobuf/proto" "strings" "time" - "fmt" ) var DaysOfWeek = map[string]string{ @@ -53,7 +53,7 @@ func IsValidMessage(message []byte, expectedServiceKey string) ([]model.Privileg var validPrivileges []model.PrivilegeMessage for _, privilege := range privileges { - fmt.Println("IVM1", privilege.ServiceKey, privilege) + fmt.Println("IVM1", privilege.ServiceKey, privilege) if IsServiceKeyValid(privilege.ServiceKey, expectedServiceKey) { validPrivileges = append(validPrivileges, model.PrivilegeMessage{ PrivilegeID: privilege.PrivilegeID, @@ -143,3 +143,14 @@ func ToJSON(data interface{}) (string, error) { } return string(result), nil } + +func CleanNullContent(answers []model.ResultAnswer) []model.ResultAnswer { + var results []model.ResultAnswer + for _, answer := range answers { + answer.Content = strings.ReplaceAll(strings.ReplaceAll(answer.Content, "`,`", "`
`"),"\n","
") + if answer.Content != "" { + results = append(results, answer) + } + } + return results +} diff --git a/workers/shortstat/timeout.go b/internal/workers/shortstat/timeout.go similarity index 94% rename from workers/shortstat/timeout.go rename to internal/workers/shortstat/timeout.go index b5108c4..dfb1366 100644 --- a/workers/shortstat/timeout.go +++ b/internal/workers/shortstat/timeout.go @@ -5,7 +5,7 @@ import ( "database/sql" "fmt" "gitea.pena/SQuiz/common/dal" - "gitea.pena/SQuiz/worker/workers" + "gitea.pena/SQuiz/worker/internal/workers" "time" ) diff --git a/workers/timeout/timeout.go b/internal/workers/timeout/timeout.go similarity index 94% rename from workers/timeout/timeout.go rename to internal/workers/timeout/timeout.go index 3c6d099..a2ba8e2 100644 --- a/workers/timeout/timeout.go +++ b/internal/workers/timeout/timeout.go @@ -4,7 +4,7 @@ import ( "context" "database/sql" "gitea.pena/SQuiz/common/dal" - "gitea.pena/SQuiz/worker/workers" + "gitea.pena/SQuiz/worker/internal/workers" "time" ) diff --git a/workers/worker.go b/internal/workers/worker.go similarity index 100% rename from workers/worker.go rename to internal/workers/worker.go diff --git a/main.go b/main.go deleted file mode 100644 index 0360e69..0000000 --- a/main.go +++ /dev/null @@ -1,10 +0,0 @@ -package main - -import ( - "github.com/skeris/appInit" - "gitea.pena/SQuiz/worker/app" -) - -func main() { - appInit.Initialize(app.New, app.Options{}) -} diff --git a/pkg/closer/closer.go b/pkg/closer/closer.go new file mode 100644 index 0000000..fdfbaf1 --- /dev/null +++ b/pkg/closer/closer.go @@ -0,0 +1,37 @@ +package closer + +import ( + "context" +) + +type Closer interface { + Close(ctx context.Context) error +} + +type CloserFunc func(ctx context.Context) error + +func (cf CloserFunc) Close(ctx context.Context) error { + return cf(ctx) +} + +type CloserGroup struct { + closers []Closer +} + +func NewCloserGroup() *CloserGroup { + return &CloserGroup{} +} + +func (cg *CloserGroup) Add(c Closer) { + cg.closers = append(cg.closers, c) +} + +func (cg *CloserGroup) Call(ctx context.Context) error { + var closeErr error + for i := len(cg.closers) - 1; i >= 0; i-- { + if err := cg.closers[i].Close(ctx); err != nil && closeErr == nil { + closeErr = err + } + } + return closeErr +} diff --git a/privilegewc/check.go b/privilegewc/check.go deleted file mode 100644 index 454bf2f..0000000 --- a/privilegewc/check.go +++ /dev/null @@ -1,67 +0,0 @@ -package privilegewc - -import ( - "context" - "fmt" - "github.com/themakers/hlog" - "gitea.pena/SQuiz/common/dal" - "gitea.pena/SQuiz/common/model" - "time" -) - -type CheckWorkerConfig struct { - TickerInterval time.Duration - DefaultData model.DefaultData - Logger hlog.Logger - ErrChan chan<- error -} - -type CheckWorker struct { - config CheckWorkerConfig - privilegeDAL *dal.DAL -} - -func NewCheckWorker(config CheckWorkerConfig, privilegeDAL *dal.DAL) *CheckWorker { - return &CheckWorker{ - config: config, - privilegeDAL: privilegeDAL, - } -} - -func (w *CheckWorker) Start(ctx context.Context) { - ticker := time.NewTicker(w.config.TickerInterval) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - fmt.Println("CHECK") - w.performScheduledTasks(ctx) - case <-ctx.Done(): - fmt.Println("Check worker terminated") - return - } - } -} - -// TODO: Maybe one query? -func (w *CheckWorker) performScheduledTasks(ctx context.Context) { - fmt.Println("CHEC0") - w.deleteExpired(ctx) -} - -func (w *CheckWorker) deleteExpired(ctx context.Context) { - expiredData, err := w.privilegeDAL.AccountRepo.GetExpired(ctx, w.config.DefaultData.UnlimID) - if err != nil { - w.config.Logger.Module("Error getting expired quizUnlimTime records") - w.config.ErrChan <- err - } - - for _, data := range expiredData { - err := w.privilegeDAL.AccountRepo.DeletePrivilegeByID(ctx, data.Privilege.ID) - if err != nil { - w.config.Logger.Module("Error deleting expired quizUnlimTime record") - w.config.ErrChan <- err - } - } -} diff --git a/tests/gigachat_test.go b/tests/gigachat_test.go new file mode 100644 index 0000000..7d43c9d --- /dev/null +++ b/tests/gigachat_test.go @@ -0,0 +1,53 @@ +package tests + +import ( + "context" + "crypto/tls" + "fmt" + "gitea.pena/SQuiz/common/model" + "gitea.pena/SQuiz/worker/internal/clients/gigachat" + "github.com/go-redis/redis/v8" + "github.com/go-resty/resty/v2" + "go.uber.org/zap" + "testing" + "time" +) + +func TestGigachat(t *testing.T) { + ctx := context.Background() + logger, _ := zap.NewDevelopment() + + redisClient := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "admin", + DB: 2, + }) + + gigaChatClient, err := gigachat.NewGigaChatClient(ctx, gigachat.Deps{ + Logger: logger, + Client: resty.New().SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}), + BaseURL: "https://gigachat.devices.sberbank.ru/api/v1", + AuthKey: "ZGM3MDY0ZjAtODM4Yi00ZTQ4LTgzMTgtZDA0ZDA3NmIwYzJjOjRkZWI4Y2NhLTc1YzUtNDg5ZS04YzY4LTVkNTdmMWU1YjU5Nw==", + RedisClient: redisClient, + }) + if err != nil { + panic(err) + } + + go gigaChatClient.TokenResearch(ctx) + + result, err := gigaChatClient.SendMsg(ctx, model.GigaChatAudience{ + Sex: false, + Age: "17-23", + }, model.Question{ + Title: "О личной жизни", + Description: "Как много у вас котят?", + }) + if err != nil { + panic(err) + } + + fmt.Println(result) + + time.Sleep(10 * time.Minute) +} diff --git a/tests/img_contents.json b/tests/img_contents.json new file mode 100644 index 0000000..8b07958 --- /dev/null +++ b/tests/img_contents.json @@ -0,0 +1,17 @@ +[ + {"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/cq64n9vot84c73evddbg","Description":"Вариант 1"} +,{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/cq64navot84c73evddcg","Description":"Вариант 2"} +,{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalmkvot84c739fusn0","Description":""} +,{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalmm7ot84c739fuso0","Description":""} +,{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalmn7ot84c739fusp0","Description":""} +,{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalmo7ot84c739fusq0","Description":""} +,{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalmp7ot84c739fusr0","Description":""} +,{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalmqfot84c739fuss0","Description":""} +,{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalmrnot84c739fust0","Description":""} +,{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalmsnot84c739fusu0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csaln27ot84c739fusv0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csaln3fot84c739fut00","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csaln47ot84c739fut10","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csaln57ot84c739fut20","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csaln6fot84c739fut30","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csaln7fot84c739fut40","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csaln8fot84c739fut50","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csaln9fot84c739fut60","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalnafot84c739fut70","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalnb7ot84c739fut80","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalncfot84c739fut90","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalndnot84c739futa0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalnenot84c739futb0","Description":""}, +{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalnfnot84c739futc0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalngvot84c739futd0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalni7ot84c739fute0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalnj7ot84c739futf0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalnjvot84c739futg0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalnkvot84c739futh0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalnlnot84c739futi0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalnn7ot84c739futj0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalno7ot84c739futk0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalnp7ot84c739futl0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalnq7ot84c739futm0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalnr7ot84c739futn0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalns7ot84c739futo0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalnt7ot84c739futp0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalnu7ot84c739futq0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalo07ot84c739futr0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalo1fot84c739futs0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalo2fot84c739futt0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalo37ot84c739futu0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalo47ot84c739futv0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalo57ot84c739fuu00","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalo67ot84c739fuu10","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalo6vot84c739fuu20","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalo87ot84c739fuu30","Description":""} +,{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalo97ot84c739fuu40","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csaloa7ot84c739fuu50","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalob7ot84c739fuu60","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalobvot84c739fuu70","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalocnot84c739fuu80","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalodfot84c739fuu90","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csaloefot84c739fuua0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalof7ot84c739fuub0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalofvot84c739fuuc0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalogvot84c739fuud0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalohvot84c739fuue0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalot7ot84c739fuuf0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalou7ot84c739fuug0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalouvot84c739fuuh0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalp07ot84c739fuui0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalp17ot84c739fuuj0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalp27ot84c739fuuk0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalp37ot84c739fuul0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalp4fot84c739fuum0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalp5vot84c739fuun0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalp6not84c739fuuo0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalp7fot84c739fuup0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalp8fot84c739fuuq0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalp9vot84c739fuur0","Description":""} +,{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalpanot84c739fuus0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalpbfot84c739fuut0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalpc7ot84c739fuuu0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalpd7ot84c739fuuv0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalpenot84c739fuv00","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalpffot84c739fuv10","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalpgfot84c739fuv20","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalph7ot84c739fuv30","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalphvot84c739fuv40","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalpinot84c739fuv50","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalpjnot84c739fuv60","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalpkfot84c739fuv70","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalpl7ot84c739fuv80","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalplvot84c739fuv90","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalpn7ot84c739fuva0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalpnvot84c739fuvb0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalpofot84c739fuvc0","Description":""},{"Image":"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/33ec973f-b4bd-4215-bb13-b98914161908/csalpp7ot84c739fuvd0","Description":""}] + + + diff --git a/tests/mail/client_tg.tmpl b/tests/mail/client_tg.tmpl new file mode 100644 index 0000000..5c9a4fa --- /dev/null +++ b/tests/mail/client_tg.tmpl @@ -0,0 +1,26 @@ +*Квиз для вашего бизнеса* + +*Поступила новая заявка с квиза "{{.QuizConfig.Theme}}"!* +Время заявки: {{ .AnswerTime }} + +Имя: {{ .AnswerContent.Name }} +{{ if .AnswerContent.Email }}Email: {{ .AnswerContent.Email }}{{ end }} +{{ if .AnswerContent.Phone }}Телефон: {{ .AnswerContent.Phone }}{{ end }} +{{ if .AnswerContent.Telegram }}Telegram: {{ .AnswerContent.Telegram }}{{ end }} +{{ if .AnswerContent.Wechat }}Wechat: {{ .AnswerContent.Wechat }}{{ end }} +{{ if .AnswerContent.Viber }}Viber: {{ .AnswerContent.Viber }}{{ end }} +{{ if .AnswerContent.Vk }}Vk: {{ .AnswerContent.Vk }}{{ end }} +{{ if .AnswerContent.Skype }}Skype: {{ .AnswerContent.Skype }}{{ end }} +{{ if .AnswerContent.Whatsup }}Whatsup: {{ .AnswerContent.Whatsup }}{{ end }} +{{ if .AnswerContent.Messenger }}Messenger: {{ .AnswerContent.Messenger }}{{ end }} +{{ if .AnswerContent.Address }}Адрес: {{ .AnswerContent.Address }}{{ end }} +{{ range $key, $value := .AnswerContent.Custom }}{{ $key }}: {{ $value }}{{ end }} + +*Ответы:* +{{ range .AllAnswers }} + {{ if index $.QuestionsMap .AnswerID }} + *{{ index $.QuestionsMap .AnswerID }}* + {{ .Content }} + {{ end }} +{{ end }} + diff --git a/tests/smtp_test.go b/tests/smtp_test.go index ce12a5c..31474a5 100644 --- a/tests/smtp_test.go +++ b/tests/smtp_test.go @@ -1,13 +1,21 @@ package tests import ( + "context" _ "embed" + "encoding/json" + "fmt" + "gitea.pena/SQuiz/common/clients" + "gitea.pena/SQuiz/common/dal" + "gitea.pena/SQuiz/common/model" + "gitea.pena/SQuiz/worker/internal/answerwc" + senders2 "gitea.pena/SQuiz/worker/internal/senders" "github.com/gofiber/fiber/v2" "github.com/pioz/faker" "github.com/stretchr/testify/assert" - "gitea.pena/SQuiz/common/model" - "gitea.pena/SQuiz/worker/answerwc" - "gitea.pena/SQuiz/worker/clients/mailclient" + "io/ioutil" + "os" + "strings" "testing" "time" ) @@ -18,22 +26,25 @@ var toClientTemplate string //go:embed mail/reminder.tmpl var reminderTemplate string +//go:embed mail/client_tg.tmpl +var tgClientTemplate string + func TestProcessMessageToSMTP(t *testing.T) { - clientDeps := mailclient.ClientDeps{ - Host: "connect.mailclient.bz", - Port: "587", - Sender: "skeris@mailing.pena.digital", + clientDeps := clients.Deps{ + SmtpHost: "connect.mailclient.bz", + SmtpPort: "587", + SmtpSender: "skeris@mailing.pena.digital", ApiKey: "P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev", SmtpApiUrl: "https://api.smtp.bz/v1/smtp/send", FiberClient: &fiber.Client{}, } - client := mailclient.NewClient(clientDeps) + client := clients.NewSmtpClient(clientDeps) recipient := "pashamullin2001@gmail.com" subject := "Test" - data := mailclient.EmailTemplateData{ + data := senders2.TemplateData{ QuizConfig: model.ResultInfo{ Theme: "

Taemplste Quiz

", }, @@ -65,7 +76,8 @@ func TestProcessMessageToSMTP(t *testing.T) { AnswerTime: time.Now().Format("Monday, 2 January 2006 г., 15:04 UTC-07:00"), } - err := client.SendMailWithAttachment(recipient, subject, toClientTemplate, data, nil) + mailSender := senders2.NewMailLeadSender(client) + err := mailSender.SendMailWithAttachment(recipient, subject, toClientTemplate, data, nil) if err != nil { t.Errorf("Error sending email: %v", err) } @@ -73,15 +85,15 @@ func TestProcessMessageToSMTP(t *testing.T) { } func TestProcessReminderToClient(t *testing.T) { - clientDeps := mailclient.ClientDeps{ - Host: "connect.mailclient.bz", - Port: "587", - Sender: "skeris@mailing.pena.digital", + clientDeps := clients.Deps{ + SmtpHost: "connect.mailclient.bz", + SmtpPort: "587", + SmtpSender: "skeris@mailing.pena.digital", ApiKey: "P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev", FiberClient: &fiber.Client{}, } - client := mailclient.NewClient(clientDeps) + client := clients.NewSmtpClient(clientDeps) recipient := "mullinp@internet.ru" subject := "Test Reminder" @@ -92,7 +104,9 @@ func TestProcessReminderToClient(t *testing.T) { Theme: "Reminder Theme", } - err := client.SendMailWithAttachment(recipient, subject, reminderTemplate, mailclient.EmailTemplateData{ + mailSender := senders2.NewMailLeadSender(client) + + err := mailSender.SendMailWithAttachment(recipient, subject, reminderTemplate, senders2.TemplateData{ QuizConfig: quizConfig, AnswerContent: model.ResultContent{}, AllAnswers: []model.ResultAnswer{}, @@ -105,26 +119,35 @@ func TestProcessReminderToClient(t *testing.T) { } func TestProcessMessageToClient(t *testing.T) { - - smtpData := mailclient.ClientDeps{ - Host: "connect.mailclient.bz", - Port: "587", - Sender: "skeris@mailing.pena.digital", + smtpData := clients.Deps{ + SmtpApiUrl: "https://api.smtp.bz/v1/smtp/send", + SmtpHost: "connect.mailclient.bz", + SmtpPort: "587", + SmtpSender: "skeris@mailing.pena.digital", ApiKey: "P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev", FiberClient: &fiber.Client{}, } - mailClient := mailclient.NewClient(smtpData) + mailClient := clients.NewSmtpClient(smtpData) + mailSender := senders2.NewMailLeadSender(mailClient) + tgSender, err := senders2.NewTgSender("6712573453:AAFqTOsgwe_j48ZQ1GzWKQDT5Nwr-SAWjz8") + assert.NoError(t, err) + + ctx := context.Background() + + repo, err := dal.New(ctx, "host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable", nil) + assert.NoError(t, err) deps := answerwc.DepsSendToClient{ - Redis: nil, - Dal: nil, - MailClient: mailClient, + Redis: nil, + Dal: repo, + LeadSenders: []senders2.LeadSender{mailSender, tgSender}, + CustomerService: nil, } errChan := make(chan<- error) - w := answerwc.NewSendToClient(deps, nil, errChan) + w := answerwc.NewSendToClient(deps, errChan) quizConfig := model.QuizConfig{ Mailing: model.ResultInfo{ @@ -138,21 +161,63 @@ func TestProcessMessageToClient(t *testing.T) { } account := model.Account{ - Email: "pashamullin2001@gmail.com", + UserID: "64f2cd7a7047f28fdabf6d9e", } + file, err := os.Open("img_contents.json") + if err != nil { + fmt.Println("Failed to open file", err) + return + } + defer file.Close() + + bytes, err := ioutil.ReadAll(file) + if err != nil { + fmt.Println("Failed to read file", err) + return + } + + var items []model.ImageContent + err = json.Unmarshal(bytes, &items) + if err != nil { + fmt.Println("Failed to unmarshal JSON:", err) + return + } + + fmt.Println(len(items)) + allAnswers := []model.ResultAnswer{ { - Content: `{"Image":"https://letsenhance.io/static/8f5e523ee6b2479e26ecc91b9c25261e/1015f/MainAfter.jpg","Description":"Gekon"}`, - AnswerID: 1, - QuestionID: 1, - }, - { + Content: "", AnswerID: 2, QuestionID: 2, }, } + answersIMG := model.ResultAnswer{ + Content: "", + AnswerID: 1, + QuestionID: 1, + } + + var imageLinks []string + + for _, item := range items { + + str, err := json.Marshal(item) + if err != nil { + fmt.Println("error marshal item to str:", err) + return + } + imageLinks = append(imageLinks, fmt.Sprintf("`%s`", string(str))) + } + + fmt.Println(len(imageLinks)) + + answersIMG.Content = strings.Join(imageLinks, ",") + + allAnswers = append(allAnswers, answersIMG) + answerContent := model.ResultContent{ Name: "Pasha", Phone: "+723456789", @@ -169,7 +234,61 @@ func TestProcessMessageToClient(t *testing.T) { answerTime := time.Now() - err := w.ProcessMessageToClient(quizConfig, questionsMap, account, allAnswers, answerContent, answerTime) + err = w.ProcessMessageToClient(ctx, answerwc.DepsProcessMsgToClient{ + QuizConfig: quizConfig, + QuestionsMap: questionsMap, + Account: account, + AllAnswers: allAnswers, + AnswerContent: answerContent, + AnswerTime: answerTime, + Quiz: &model.Quiz{ + Id: 26324, + AccountId: "64f2cd7a7047f28fdabf6d9e", + }, + }) + err = w.ProcessReminderToClient(ctx, account, model.QuizConfig{ + Mailing: model.ResultInfo{ + When: "email", + Theme: fmt.Sprintf("не удалось отправить заявку по опросу\"%s\"", "test"), + Reply: "noreply@pena.digital", + ReplName: "Reminder", + }, + }, &model.Quiz{ + Id: 26324, + AccountId: "64f2cd7a7047f28fdabf6d9e", + }) assert.NoError(t, err) } + +//func TestManyImg(t *testing.T) { +// smtpData := clients.Deps{ +// SmtpApiUrl: "https://api.smtp.bz/v1/smtp/send", +// SmtpHost: "connect.mailclient.bz", +// SmtpPort: "587", +// SmtpSender: "skeris@mailing.pena.digital", +// ApiKey: "P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev", +// FiberClient: &fiber.Client{}, +// } +// +// mailClient := clients.NewSmtpClient(smtpData) +// mailSender := senders.NewMailLeadSender(mailClient) +// tgSender, err := senders.NewTgSender("6712573453:AAFqTOsgwe_j48ZQ1GzWKQDT5Nwr-SAWjz8") +// assert.NoError(t, err) +// +// ctx := context.Background() +// +// repo, err := dal.New(ctx, "host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable", nil) +// assert.NoError(t, err) +// +// deps := answerwc.DepsSendToClient{ +// Redis: nil, +// Dal: repo, +// LeadSenders: []senders.LeadSender{mailSender, tgSender}, +// CustomerService: nil, +// } +// +// errChan := make(chan<- error) +// +// w := answerwc.NewSendToClient(deps, errChan) +//} diff --git a/tests/tg_sender_test.go b/tests/tg_sender_test.go new file mode 100644 index 0000000..be380d0 --- /dev/null +++ b/tests/tg_sender_test.go @@ -0,0 +1,48 @@ +package tests + +import ( + "gitea.pena/SQuiz/common/model" + senders2 "gitea.pena/SQuiz/worker/internal/senders" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func Test_Tg_Sender(t *testing.T) { + tg_Sender, err := senders2.NewTgSender("6712573453:AAFqTOsgwe_j48ZQ1GzWKQDT5Nwr-SAWjz8") + assert.NoError(t, err) + err = tg_Sender.SendLead(senders2.LeadData{ + To: int64(-1002217604546), + Subject: "test_TG_Sender", + Template: tgClientTemplate, + TemplateData: senders2.TemplateData{ + QuizConfig: model.ResultInfo{ + Theme: "Taemplste Quiz", + }, + AnswerContent: model.ResultContent{ + Name: "Pasha", + Phone: "+723456789", + Email: "test@example.com", + //Adress: "chtoto tam", + Telegram: "@test", + Wechat: "test_wechat", + Viber: "+723456789", + Vk: "test_vk", + Skype: "test_skype", + Whatsup: "test_whatsup", + Messenger: "test_messenger", + }, + AllAnswers: []model.ResultAnswer{ + {AnswerID: 1, QuestionID: 1, Content: "Pasha", CreatedAt: time.Now()}, + {AnswerID: 2, QuestionID: 2, Content: "From a friend", CreatedAt: time.Now()}, + }, + QuestionsMap: map[uint64]string{ + 1: "How did you hear about us?", + 2: "How did you hear about us?", + }, + AnswerTime: time.Now().Format("Monday, 2 January 2006 г., 15:04 UTC-07:00"), + }, + }) + + assert.NoError(t, err) +}