package answerwc import ( "context" _ "embed" "encoding/json" "errors" "fmt" "github.com/go-redis/redis/v8" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors" "penahub.gitlab.yandexcloud.net/backend/quiz/worker/internal/senders" "penahub.gitlab.yandexcloud.net/backend/quiz/worker/internal/wctools" "penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/customer_clients" "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 } }