diff --git a/answerwc/respondent.go b/answerwc/respondent.go index 5b4abc3..3aa7316 100644 --- a/answerwc/respondent.go +++ b/answerwc/respondent.go @@ -5,10 +5,9 @@ import ( "encoding/json" "fmt" "github.com/go-redis/redis/v8" - "github.com/themakers/hlog" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" - "penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/clients/mailclient" + "penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/senders" "penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/wctools" "time" ) @@ -16,19 +15,17 @@ import ( type DepsRespWorker struct { Redis *redis.Client Dal *dal.DAL - MailClient *mailclient.Client + MailClient *senders.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 := senders.TemplateData{ QuizConfig: quizConfig.Mailing, AnswerContent: answerContent, AllAnswers: allAnswers, diff --git a/answerwc/to_client.go b/answerwc/to_client.go index 323321e..20d29cd 100644 --- a/answerwc/to_client.go +++ b/answerwc/to_client.go @@ -2,32 +2,33 @@ package answerwc import ( "context" + "database/sql" _ "embed" "encoding/json" "fmt" - "github.com/themakers/hlog" + "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/worker.git/clients/customer" - "penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/clients/mailclient" + "penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/senders" "penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/wctools" + "penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/customer_clients" "time" - - "github.com/go-redis/redis/v8" ) type DepsSendToClient struct { Redis *redis.Client Dal *dal.DAL - MailClient *mailclient.Client - CustomerService customer.CustomerServiceClient + LeadSenders []senders.LeadSender + CustomerService *customer_clients.CustomersClient } type SendToClient struct { - deps DepsSendToClient - logger hlog.Logger - errChan chan<- error + redis *redis.Client + dal *dal.DAL + leadSenders []senders.LeadSender + customerService *customer_clients.CustomersClient + errChan chan<- error } type PendingTasks struct { @@ -41,11 +42,12 @@ var toClientTemplate string //go:embed mail/reminder.tmpl var reminderTemplate string -func NewSendToClient(deps DepsSendToClient, logger hlog.Logger, errChan chan<- error) *SendToClient { +func NewSendToClient(deps DepsSendToClient, errChan chan<- error) *SendToClient { return &SendToClient{ - deps: deps, - logger: logger, - errChan: errChan, + redis: deps.Redis, + dal: deps.Dal, + customerService: deps.CustomerService, + errChan: errChan, } } @@ -59,7 +61,6 @@ func (w *SendToClient) Start(ctx context.Context) { w.processPendingAnswer(ctx) case <-ctx.Done(): - w.logger.Module("To client worker terminated") return } @@ -67,7 +68,7 @@ func (w *SendToClient) Start(ctx context.Context) { } func (w *SendToClient) processPendingAnswer(ctx context.Context) { - pendingAnswers, err := w.deps.Redis.Keys(ctx, "answer:*").Result() + pendingAnswers, err := w.redis.Keys(ctx, "answer:*").Result() if err != nil { fmt.Println("Error getting keys from redis") w.errChan <- err @@ -79,7 +80,7 @@ func (w *SendToClient) processPendingAnswer(ctx context.Context) { for _, key := range pendingAnswers { func() { fmt.Println("ANS1", key) - answerJSON, err := w.deps.Redis.GetDel(ctx, key).Result() + answerJSON, err := w.redis.GetDel(ctx, key).Result() if err == redis.Nil { return } else if err != nil { @@ -91,7 +92,7 @@ func (w *SendToClient) processPendingAnswer(ctx context.Context) { 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() + _ = w.redis.Set(ctx, key, answerJSON, 0).Err() } }() @@ -110,14 +111,14 @@ func (w *SendToClient) processPendingAnswer(ctx context.Context) { return } - allAnswers, err := w.deps.Dal.WorkerAnsRepo.GetAllAnswersByQuizID(ctx, answer.Session) + allAnswers, 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 } - questionsMap, sortedallAnswers, err := w.deps.Dal.QuestionRepo.GetMapQuestions(ctx, allAnswers) + questionsMap, sortedallAnswers, err := w.dal.QuestionRepo.GetMapQuestions(ctx, allAnswers) fmt.Println("ANS5", err) if err != nil { w.reportError(err, "Error getting questionsMap") @@ -128,14 +129,14 @@ func (w *SendToClient) processPendingAnswer(ctx context.Context) { return } - quizConfig, accountId, err := w.deps.Dal.QuizRepo.GetQuizConfig(ctx, answer.QuizId) + 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.deps.Dal.QuizRepo.GetQuizById(ctx, accountId, answer.QuizId) + 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") @@ -148,21 +149,30 @@ func (w *SendToClient) processPendingAnswer(ctx context.Context) { quizConfig.Mailing.Theme = quiz.Name } - account, privileges, err := w.deps.Dal.AccountRepo.GetAccAndPrivilegeByEmail(ctx, accountId) + 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, quiz.Name, quizConfig, questionsMap, privileges, account, sortedallAnswers, answerContent, answer.CreatedAt) + 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.deps.Redis.Set(ctx, fmt.Sprintf("%s:%s", account.ID, key), answerJSON, 0).Err() + 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 @@ -172,45 +182,70 @@ func (w *SendToClient) processPendingAnswer(ctx context.Context) { } } -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) (bool, error) { +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 +} - err := w.notificationCustomer(account, privileges) +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(privileges) { - err := w.ProcessMessageToClient(quizConfig, questionsMap, account, allAnswers, answerContent, answerTime) + 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(privileges) + privilege := wctools.HasQuizCntPrivilege(data.privileges) if privilege != nil { - err := w.ProcessMessageToClient(quizConfig, questionsMap, account, allAnswers, answerContent, answerTime) + 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.deps.Dal.AccountRepo.UpdatePrivilegeAmount(ctx, privilege.ID, 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{ - email: account.Email, - theme: quizName, + w.checkAndSendTaskReminders(sendTaskRemindersDeps{ + account: data.account, + theme: data.quiz.Name, config: model.QuizConfig{ Mailing: model.ResultInfo{ When: "email", - Theme: fmt.Sprintf("не удалось отправить заявку по опросу\"%s\"", quizName), + Theme: fmt.Sprintf("не удалось отправить заявку по опросу\"%s\"", data.quiz.Name), Reply: "noreply@pena.digital", ReplName: "Reminder", }, @@ -224,7 +259,7 @@ func (w *SendToClient) recordPendingTasks(ctx context.Context, Email string, qui key := fmt.Sprintf("pending_tasks:%s", Email) var pendingTasks PendingTasks - val, err := w.deps.Redis.HGet(ctx, key, "data").Result() + val, err := w.redis.HGet(ctx, key, "data").Result() if err == nil { err := json.Unmarshal([]byte(val), &pendingTasks) if err != nil { @@ -243,7 +278,7 @@ func (w *SendToClient) recordPendingTasks(ctx context.Context, Email string, qui return err } - err = w.deps.Redis.HSet(ctx, key, "data", string(pendingTasksJSON)).Err() + err = w.redis.HSet(ctx, key, "data", string(pendingTasksJSON)).Err() if err != nil { return err } @@ -252,31 +287,30 @@ func (w *SendToClient) recordPendingTasks(ctx context.Context, Email string, qui } type sendTaskRemindersDeps struct { - email, theme string - config model.QuizConfig + account model.Account + theme string + config model.QuizConfig } -func (w *SendToClient) checkAndSendTaskReminders(ctx context.Context, deps sendTaskRemindersDeps) { - err := w.processReminderToClient(deps.email, deps.config) +func (w *SendToClient) checkAndSendTaskReminders(data sendTaskRemindersDeps) { + err := w.processReminderToClient(data.account, data.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 { +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) { - rawDetail, err := wctools.ToJSON(privilege) - historyData := &customer.History{ - UserID: account.UserID, - Comment: fmt.Sprintf("Привилегия %s просрочена", privilege.PrivilegeID), - Key: "privilege_expired", - RawDetails: rawDetail, + 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.deps.CustomerService.InsertHistory(context.Background(), historyData) + err := w.customerService.InsertHistory(ctx, historyData) if err != nil { return err } @@ -284,18 +318,13 @@ func (w *SendToClient) notificationCustomer(account model.Account, privileges [] } 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, + 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.deps.CustomerService.InsertHistory(context.Background(), historyData) + err := w.customerService.InsertHistory(ctx, historyData) if err != nil { return err } @@ -305,50 +334,83 @@ func (w *SendToClient) notificationCustomer(account model.Account, privileges [] 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) error { - theme := quizConfig.Mailing.Theme - quizConfig.Mailing.Theme = quizConfig.Mailing.Reply +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 +} - data := mailclient.EmailTemplateData{ - QuizConfig: quizConfig.Mailing, - AnswerContent: answerContent, - AllAnswers: allAnswers, - QuestionsMap: questionsMap, +// сделал экспортируемым для теста +func (w *SendToClient) ProcessMessageToClient(ctx context.Context, constructData DepsProcessMsgToClient) error { + leadTargetForAll, err := w.dal.AccountRepo.GetLeadTarget(ctx, constructData.quiz.AccountId, 0) + if err != nil { + return err + } + leadTargetForQuiz, err := w.dal.AccountRepo.GetLeadTarget(ctx, constructData.quiz.AccountId, int32(constructData.quiz.Id)) + if err != nil && err != sql.ErrNoRows { + return err + } + if len(leadTargetForQuiz) > 0 { + leadTargetForAll = append(leadTargetForAll, leadTargetForQuiz...) } - dayOfWeek := wctools.DaysOfWeek[answerTime.Format("Monday")] - monthOfYear := wctools.MonthsOfYear[answerTime.Format("January")] + 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, - answerTime.Day(), + constructData.answerTime.Day(), monthOfYear, - answerTime.Year(), - answerTime.Hour(), - answerTime.Minute(), - answerTime.Format("-07:00"), + constructData.answerTime.Year(), + constructData.answerTime.Hour(), + constructData.answerTime.Minute(), + constructData.answerTime.Format("-07:00"), ) data.AnswerTime = formattedTime - fmt.Println("SUBJECT", theme, account.Email) + 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, + }) + } - err := w.deps.MailClient.SendMailWithAttachment(account.Email, theme, toClientTemplate, data, nil) - if err != nil { - return err + 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(email string, quizConfig model.QuizConfig) error { - - data := mailclient.EmailTemplateData{ +// todo email +func (w *SendToClient) processReminderToClient(account model.Account, quizConfig model.QuizConfig) error { + data := senders.TemplateData{ QuizConfig: model.ResultInfo{ When: quizConfig.Mailing.When, Theme: quizConfig.Mailing.Theme, - Reply: email, + Reply: "email", ReplName: quizConfig.Mailing.ReplName, }, AnswerContent: model.ResultContent{}, @@ -356,11 +418,20 @@ func (w *SendToClient) processReminderToClient(email string, quizConfig model.Qu QuestionsMap: nil, } - fmt.Println("PRTC", data, email, quizConfig) + //fmt.Println("PRTC", data, email, quizConfig) - err := w.deps.MailClient.SendMailWithAttachment(email, quizConfig.Mailing.Theme, reminderTemplate, data, nil) - if err != nil { - return err + leadData := senders.LeadData{ + To: "email", + Subject: quizConfig.Mailing.Theme, + Template: reminderTemplate, + TemplateData: data, + } + + for _, sender := range w.leadSenders { + err := sender.SendLead(leadData) + if err != nil { + w.reportError(err, fmt.Sprintf("Error sending lead through %s", sender.Name())) + } } return nil diff --git a/app/app.go b/app/app.go index a4c42dd..e109862 100644 --- a/app/app.go +++ b/app/app.go @@ -10,15 +10,15 @@ import ( "github.com/skeris/appInit" "github.com/themakers/hlog" "go.uber.org/zap" - "google.golang.org/grpc" + "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/clients" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal" - "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" "penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/answerwc" - "penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/clients/customer" - "penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/clients/mailclient" "penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/privilegewc" + "penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/senders" "penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/workers/shortstat" "penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/workers/timeout" + "penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/customer_clients" + "time" ) @@ -49,19 +49,16 @@ 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"` + KafkaBroker string `env:"KAFKA_BROKER" default:"localhost:6379"` + KafkaTopic string `env:"KAFKA_TOPIC" default:"test-topic"` 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"` + PostgresCredentials string `env:"PG_CRED" default:"host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"` RedisHost string `env:"REDIS_HOST" default:"localhost:6379"` - RedisPassword string `env:"REDIS_PASSWORD"` + RedisPassword string `env:"REDIS_PASSWORD" default:"admin"` RedisDB uint64 `env:"REDIS_DB" default:"2"` SmtpHost string `env:"SMTP_HOST" default:"connect.mailclient.bz"` SmtpPort string `env:"SMTP_PORT" default:"587"` @@ -70,7 +67,8 @@ type Options struct { 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"` + CustomerServiceAddress string `env:"CUSTOMER_SERVICE_ADDRESS" default:"localhost:9001"` + TgToken string `env:"TG_TOKEN"` } func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.CommonApp, error) { @@ -126,21 +124,26 @@ func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.Co DB: int(options.RedisDB), }) - smtpData := mailclient.ClientDeps{ - Host: options.SmtpHost, - Port: options.SmtpPort, - Sender: options.SmtpSender, + mailClent := clients.NewSmtpClient(clients.Deps{ + SmtpHost: options.SmtpHost, + SmtpPort: options.SmtpPort, + SmtpSender: options.SmtpSender, ApiKey: options.SmtpApiKey, SmtpApiUrl: options.SmtpApiUrl, - } + }) - mailClient := mailclient.NewClient(smtpData) - - customerServiceConn, err := grpc.Dial(options.CustomerServiceAddress, grpc.WithInsecure()) + tgSender, err := senders.NewTgSender(options.TgToken) if err != nil { + fmt.Println(err) return nil, err } - customerServiceClient := customer.NewCustomerServiceClient(customerServiceConn) + mailSender := senders.NewMailLeadSender(mailClent) + leadSenders := []senders.LeadSender{mailSender, tgSender} + + customerClient := customer_clients.NewCustomersClient(customer_clients.CustomersClientDeps{ + Logger: zapLogger, + CustomerServiceHost: options.CustomerServiceAddress, + }) minioClient, err := minio.New(options.MinioEP, &minio.Options{ Creds: credentials.NewStaticV4(options.MinioAK, options.MinioSK, ""), @@ -161,7 +164,6 @@ func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.Co KafkaTopic: options.KafkaTopic, ServiceKey: options.ServiceName, TickerInterval: time.Second * 10, - Logger: logger, ErrChan: errChan, }, redisClient, pgdal) if err != nil { @@ -169,31 +171,29 @@ func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.Co 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) + checkWorker := privilegewc.NewCheckWorker(privilegewc.Deps{ + PrivilegeIDsDays: []string{"quizUnlimTime", "squizHideBadge"}, + PrivilegeIDsCount: []string{"quizCnt", "quizManual"}, + TickerInterval: time.Minute, + PrivilegeDAL: pgdal, + CustomerClient: customerClient, + }, errChan) go kafkaWorker.Start(ctx) go checkWorker.Start(ctx) + toClientWorker := answerwc.NewSendToClient(answerwc.DepsSendToClient{ Redis: redisClient, Dal: pgdal, - MailClient: mailClient, - CustomerService: customerServiceClient, - }, logger, errChan) + LeadSenders: leadSenders, + CustomerService: customerClient, + }, errChan) toRespWorker := answerwc.NewRespWorker(answerwc.DepsRespWorker{ Redis: redisClient, Dal: pgdal, - MailClient: mailClient, - }, logger, errChan) + MailClient: mailSender, + }, errChan) go toClientWorker.Start(ctx) go toRespWorker.Start(ctx) 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 97c2243..0000000 --- a/clients/mailclient/client.go +++ /dev/null @@ -1,131 +0,0 @@ -package mailclient - -import ( - "bytes" - _ "embed" - "fmt" - "github.com/gofiber/fiber/v2" - "html/template" - "mime/multipart" - "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/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 - 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, - }); 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/deployments/test/docker-compose.yaml b/deployments/test/docker-compose.yaml index b66713c..0f498a0 100644 --- a/deployments/test/docker-compose.yaml +++ b/deployments/test/docker-compose.yaml @@ -78,7 +78,7 @@ services: init: true build: context: ../.. - dockerfile: TestsDockerfile + dockerfile: testDockerfile depends_on: test-postgres: condition: service_healthy @@ -86,8 +86,8 @@ services: # condition: service_healthy # volumes: # - ./../..:/app:ro - # command: [ "go", "test", "./tests", "-run", "TestFoo" ] - command: [ "go", "test", "-parallel", "1", "./tests" ] + # command: [ "go", "test", "./test", "-run", "TestFoo" ] + command: [ "go", "test", "-parallel", "1", "./test" ] networks: - penatest diff --git a/go.mod b/go.mod index dde7267..6bab2d9 100644 --- a/go.mod +++ b/go.mod @@ -1,23 +1,21 @@ module penahub.gitlab.yandexcloud.net/backend/quiz/worker.git -go 1.21.4 +go 1.22.0 + +toolchain go1.22.2 require ( github.com/go-redis/redis/v8 v8.11.5 github.com/gofiber/fiber/v2 v2.52.4 github.com/golang/protobuf v1.5.4 - github.com/minio/minio-go/v7 v7.0.69 github.com/pioz/faker v1.7.3 github.com/skeris/appInit v1.0.2 github.com/stretchr/testify v1.8.4 - github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf github.com/twmb/franz-go v1.17.0 - go.uber.org/zap v1.27.0 - golang.org/x/net v0.25.0 - google.golang.org/grpc v1.64.0 - google.golang.org/protobuf v1.34.1 - penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240617173955-a34278088ddd - + golang.org/x/net v0.23.0 + gopkg.in/tucnak/telebot.v2 v2.5.0 + penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240612083524-11882ffe22cf + penahub.gitlab.yandexcloud.net/pena-services/customer v1.0.1-0.20240608222239-5c78187bf014 ) require ( @@ -25,10 +23,10 @@ require ( 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 ( + github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.8 // indirect @@ -38,10 +36,12 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/minio-go/v7 v7.0.69 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // 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/rs/xid v1.5.0 // indirect @@ -50,11 +50,14 @@ require ( github.com/valyala/fasthttp v1.54.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.23.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect + google.golang.org/grpc v1.64.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240527160654-bd1c2126bc6c // indirect + penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240607202348-efe5f2bf3e8c // indirect ) diff --git a/go.sum b/go.sum index e7ad648..1a92596 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,9 @@ github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ib github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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= @@ -69,12 +70,16 @@ github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu 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/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.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.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/skeris/appInit v1.0.2 h1:Hr4KbXYd6kolTVq4cXGqDpgnpmaauiOiKizA1+Ep4KQ= @@ -82,11 +87,10 @@ github.com/skeris/appInit v1.0.2/go.mod h1:4ElEeXWVGzU3dlYq/eMWJ/U5hd+LKisc1z3+y 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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -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.17.0 h1:hawgCx5ejDHkLe6IwAtFWwxi3OU4OztSTl7ZV5rwkYk= github.com/twmb/franz-go v1.17.0/go.mod h1:NreRdJ2F7dziDY/m6VyspWd6sNxHKXdMZI42UfQ3GXM= github.com/twmb/franz-go/pkg/kmsg v1.8.0 h1:lAQB9Z3aMrIP9qF9288XcFf/ccaSxEitNA1CDTEIeTA= @@ -116,8 +120,8 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= @@ -141,13 +145,16 @@ google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLp google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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= @@ -155,9 +162,9 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C 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= -penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240527160654-bd1c2126bc6c h1:jxnyIeC2CNDNmfdFx2qnLS4Qd0v5ocYrY9X+OL9qsvc= -penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240527160654-bd1c2126bc6c/go.mod h1:lTmpjry+8evVkXWbEC+WMOELcFkRD1lFMc7J09mOndM= -penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240617151847-e1322a679dd1 h1:KCQf7PmSq63YemEF5s81f7lXsewWFQjIr0IeIWxzBVo= -penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240617151847-e1322a679dd1/go.mod h1:n66zm88Dh12+idyfqh0vU5nd9BZYxM6Pv0XYnmy0398= -penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240617173955-a34278088ddd h1:/FW9GjEbxXWD5/e3oUGn4iKihhc77FvfDPnVqL1SQvM= -penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240617173955-a34278088ddd/go.mod h1:n66zm88Dh12+idyfqh0vU5nd9BZYxM6Pv0XYnmy0398= +penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240607202348-efe5f2bf3e8c h1:CWb4UcuNXhd1KTNOmy2U0TJO4+Qxgxrj5cwkyFqbgrk= +penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240607202348-efe5f2bf3e8c/go.mod h1:+bPxq2wfW5S1gd+83vZYmHm33AE7nEBfznWS8AM1TKE= +penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240612083524-11882ffe22cf h1:cTmv0YZE1B+ofsWfHYEiNxzToWKMy12rVW3cPOrtp30= +penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240612083524-11882ffe22cf/go.mod h1:n66zm88Dh12+idyfqh0vU5nd9BZYxM6Pv0XYnmy0398= +penahub.gitlab.yandexcloud.net/pena-services/customer v1.0.1-0.20240608222239-5c78187bf014 h1:ziG55nv824SGFZ02AfagKQC5D4ODirGXnpVPQTL6YFA= +penahub.gitlab.yandexcloud.net/pena-services/customer v1.0.1-0.20240608222239-5c78187bf014/go.mod h1:hIMkN5Xe01vAVaX22QWsGD87Oi93IfX1hJGqxy0oJbE= diff --git a/privilegewc/check.go b/privilegewc/check.go index 291c1f6..4440f59 100644 --- a/privilegewc/check.go +++ b/privilegewc/check.go @@ -3,40 +3,49 @@ package privilegewc import ( "context" "fmt" - "github.com/themakers/hlog" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" + "penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/customer_clients" "time" ) -type CheckWorkerConfig struct { - TickerInterval time.Duration - DefaultData model.DefaultData - Logger hlog.Logger - ErrChan chan<- error +type Deps struct { + PrivilegeDAL *dal.DAL + TickerInterval time.Duration + PrivilegeIDsDays []string + PrivilegeIDsCount []string + CustomerClient *customer_clients.CustomersClient } type CheckWorker struct { - config CheckWorkerConfig - privilegeDAL *dal.DAL + privilegeDAL *dal.DAL + tickerInterval time.Duration + errChan chan<- error + privilegeIDsDays []string + privilegeIDsCount []string + customerClient *customer_clients.CustomersClient } -func NewCheckWorker(config CheckWorkerConfig, privilegeDAL *dal.DAL) *CheckWorker { +func NewCheckWorker(deps Deps, errChan chan<- error) *CheckWorker { return &CheckWorker{ - config: config, - privilegeDAL: privilegeDAL, + 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.config.TickerInterval) + ticker := time.NewTicker(w.tickerInterval) defer ticker.Stop() for { select { case <-ticker.C: fmt.Println("CHECK") - w.performScheduledTasks(ctx) + w.deleteExpired(ctx) case <-ctx.Done(): fmt.Println("Check worker terminated") return @@ -44,24 +53,44 @@ func (w *CheckWorker) Start(ctx context.Context) { } } -// 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 + 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.config.Logger.Module("Error deleting expired quizUnlimTime record") - w.config.ErrChan <- err + 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/privilegewc/consumer.go index 4ab26fe..334cc89 100644 --- a/privilegewc/consumer.go +++ b/privilegewc/consumer.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "github.com/go-redis/redis/v8" - "github.com/themakers/hlog" "github.com/twmb/franz-go/pkg/kgo" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" @@ -19,7 +18,6 @@ type Config struct { KafkaTopic string ServiceKey string TickerInterval time.Duration - Logger hlog.Logger ErrChan chan<- error } diff --git a/clients/mailclient/utils.go b/senders/common.go similarity index 73% rename from clients/mailclient/utils.go rename to senders/common.go index 779dae0..768db00 100644 --- a/clients/mailclient/utils.go +++ b/senders/common.go @@ -1,71 +1,63 @@ -package mailclient +package senders import ( "bytes" - "crypto/rand" + _ "embed" "encoding/json" "fmt" "golang.org/x/net/html" "html/template" - "io" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" "strings" ) -type LineWriter struct { - w io.Writer - length int +//go:embed template/client_mail.tmpl +var toClientMailTemplate string + +//go:embed template/client_tg.tmpl +var toClientTgTemplate string + +//go:embed template/client_whatsapp.tmpl +var toClientWhatsAppTemplate string + +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 } -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{ @@ -92,8 +84,8 @@ func SplitContent(content string) template.HTML { return template.HTML(content) } -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/senders/mail_sender.go b/senders/mail_sender.go new file mode 100644 index 0000000..3217ff1 --- /dev/null +++ b/senders/mail_sender.go @@ -0,0 +1,41 @@ +package senders + +import "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/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, toClientMailTemplate, 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/senders/template/client_mail.tmpl b/senders/template/client_mail.tmpl new file mode 100644 index 0000000..fe36b93 --- /dev/null +++ b/senders/template/client_mail.tmpl @@ -0,0 +1,537 @@ + + +
+ + +
+ ![]() |
+
+ Квиз для вашего бизнеса + |
+ ||||||||||||||||||||||||
+ + Поступила новая заявка с квиза “{{.QuizConfig.Theme}}”! ++ |
+ |||||||||||||||||||||||||
+ + Время заявки: {{ .AnswerTime }} + + |
+ |||||||||||||||||||||||||
+ + Посмотреть в личном кабинете + + | +|||||||||||||||||||||||||
+ + Контакты ++ |
+ |||||||||||||||||||||||||
+
|
+ |||||||||||||||||||||||||
+ + Ответы ++ |
+ |||||||||||||||||||||||||
+
|
+ |||||||||||||||||||||||||
+ + quiz.pena.digital + + | +