Compare commits

...

4 Commits

Author SHA1 Message Date
e46acea3f1 fix var imgs
Some checks failed
Deploy / CreateImage (push) Successful in 2m55s
Deploy / ValidateConfig (push) Successful in 27s
Deploy / MigrateDatabase (push) Failing after 57s
Deploy / DeployService (push) Has been skipped
2025-06-29 18:08:11 +03:00
011e63dfa1 conf: provide gigachat notify bot token
All checks were successful
Deploy / CreateImage (push) Successful in 2m58s
Deploy / ValidateConfig (push) Successful in 28s
Deploy / MigrateDatabase (push) Successful in 48s
Deploy / DeployService (push) Successful in 27s
2025-06-18 16:53:46 +03:00
b3125d41be added notification logic if gigachat tokens run out 2025-06-18 13:47:54 +00:00
88f7f29259 fix: if topic not exist in kafka - error
All checks were successful
Deploy / CreateImage (push) Successful in 3m4s
Deploy / ValidateConfig (push) Successful in 27s
Deploy / MigrateDatabase (push) Successful in 48s
Deploy / DeployService (push) Successful in 27s
2025-06-18 13:46:46 +00:00
12 changed files with 171 additions and 30 deletions

@ -23,7 +23,12 @@ func main() {
err = validate.ValidateKafka([]string{cfg.KafkaBrokers}, cfg.KafkaTopicTariff)
if err != nil {
log.Fatalf("error validating kafka: %v", err)
log.Fatalf("error validating kafka Tariff: %v", err)
}
err = validate.ValidateKafka([]string{cfg.KafkaBrokers}, cfg.KafkaTopicGigaChat)
if err != nil {
log.Fatalf("error validating kafka GigaChat: %v", err)
}
err = validate.ValidateRedis(cfg.RedisHost, cfg.RedisPassword, int(cfg.RedisDB))
@ -78,5 +83,13 @@ func validateNotEmpty(cfg initialize.Config) error {
return errors.New("api url is not be empty")
}
if cfg.GigaChatApiBaseURL == "" {
return errors.New("giga chat api base url is not be empty")
}
if cfg.GigaChatApiAuthKey == "" {
return errors.New("giga chat api key is not be empty")
}
return nil
}

@ -20,3 +20,5 @@ KAFKA_GROUP_GIGA_CHAT=""
GIGA_CHAT_API_AUTH_KEY=Y2MzZWUxZDMtZGE5MC00ZTFjLWI5YzItM2ViMTZmMDM0YTkwOmY1NTlkOGM3LWUyNmQtNGUwMC1hODE0LTJlYjQ5NDA5ODdjMQ==
GIGA_CHAT_API_BASE_URL="https://gigachat.devices.sberbank.ru/api/v1"
TELEGRAM_TOKEN=6112841016:AAH2nO1c6mqfMewBvHwdXCDp5PCclOuc99
NOTIFY_CHANNEL_ID=-1002338593104
NOTIFY_TELEGRAM_TOKEN=6112841016:AAH2nO1c6mqfMewBvHwdXCDp5PCclOuc99s

2
go.mod

@ -5,7 +5,7 @@ go 1.23.2
toolchain go1.23.4
require (
gitea.pena/PenaSide/common v0.0.0-20250421103113-7e4b3ae9e1e0
gitea.pena/PenaSide/common v0.0.0-20250609100303-3b7c00cc97bc
gitea.pena/PenaSide/customer v0.0.0-20250518194954-882ec684be86
gitea.pena/PenaSide/hlog v0.0.0-20241125221102-a54c29c002a9
gitea.pena/SQuiz/common v0.0.0-20250610100937-ce7096a3dd37

2
go.sum

@ -1,5 +1,7 @@
gitea.pena/PenaSide/common v0.0.0-20250421103113-7e4b3ae9e1e0 h1:+gvpAPo1+1WtCpA+QaCWNy4R9/cIERBBzrVSYrx7hNo=
gitea.pena/PenaSide/common v0.0.0-20250421103113-7e4b3ae9e1e0/go.mod h1:91EuBCgcqgJ6mG36n2pds8sPwwfaJytLWOzY3h2YFKU=
gitea.pena/PenaSide/common v0.0.0-20250609100303-3b7c00cc97bc h1:xwojlRrEl2hjZxgoMqSz8pkWmQRYq7LsDdeAExtgyQE=
gitea.pena/PenaSide/common v0.0.0-20250609100303-3b7c00cc97bc/go.mod h1:91EuBCgcqgJ6mG36n2pds8sPwwfaJytLWOzY3h2YFKU=
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/customer v0.0.0-20250518194954-882ec684be86 h1:NjY7t0aerJqCcHez74sI3mQLjP3Yhc3jYG/n1/k279Y=

@ -2,13 +2,11 @@ package app
import (
"context"
"crypto/tls"
"errors"
"fmt"
"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"
@ -16,7 +14,6 @@ import (
"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"
)
@ -85,7 +82,11 @@ func New(ctx context.Context, cfg initialize.Config, build Build) error {
return err
}
clients := initialize.NewClients(cfg, zapLogger)
clients, err := initialize.NewClients(ctx, cfg, zapLogger, redisClient)
if err != nil {
zapLogger.Error("failed init clients", zap.Error(err))
return err
}
// tgSender, err := senders.NewTgSender(options.TgToken)
// if err != nil {
@ -140,26 +141,16 @@ func New(ctx context.Context, cfg initialize.Config, build Build) error {
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)
go clients.GigaChatClient.TokenResearch(ctx)
go clients.GigaChatClient.MonitorTokenBalance(ctx)
fmt.Println("INIT GGC WORKER", cfg.KafkaTopicGigaChat)
gigaChatWorker, err := gigachatwc.NewGigaChatTaskScheduler(gigachatwc.Deps{
KafkaBrokers: cfg.KafkaBrokers,
KafkaTopic: cfg.KafkaTopicGigaChat,
KafkaGroup: cfg.KafkaGroupGigaChat,
GigaChatClient: gigaChatClient,
GigaChatClient: clients.GigaChatClient,
Logger: zapLogger,
Dal: pgdal,
})

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"gitea.pena/SQuiz/common/model"
"gitea.pena/SQuiz/worker/internal/senders"
"github.com/go-redis/redis/v8"
"github.com/go-resty/resty/v2"
"github.com/google/uuid"
@ -18,6 +19,8 @@ type Deps struct {
BaseURL string
AuthKey string
RedisClient *redis.Client
TgSender *senders.TgSender
TgChatID int64
}
type GigaChatClient struct {
@ -26,6 +29,8 @@ type GigaChatClient struct {
baseURL string
authKey string
redisClient *redis.Client
tgSender *senders.TgSender
tgChatID int64
}
func NewGigaChatClient(ctx context.Context, deps Deps) (*GigaChatClient, error) {
@ -35,6 +40,9 @@ func NewGigaChatClient(ctx context.Context, deps Deps) (*GigaChatClient, error)
baseURL: deps.BaseURL,
authKey: deps.AuthKey,
redisClient: deps.RedisClient,
tgSender: deps.TgSender,
tgChatID: deps.TgChatID,
}
if err := client.updateToken(ctx); err != nil {
@ -110,7 +118,7 @@ func (r *GigaChatClient) TokenResearch(ctx context.Context) {
select {
case <-ticker.C:
ttl, err := r.redisClient.TTL(ctx, "gigachat_token").Result()
fmt.Println("GGCHATtoken", ttl, err, ttl<2*time.Minute)
fmt.Println("GGCHATtoken", ttl, err, ttl < 2*time.Minute)
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))
@ -148,7 +156,7 @@ func (r *GigaChatClient) updateToken(ctx context.Context) error {
}
ttl := time.Until(time.Unix(int64(respData.ExpiresAt/1000), 0))
fmt.Println("GGCTOKENEXP", respData.ExpiresAt, ttl, ttl<2*time.Minute, time.Now())
fmt.Println("GGCTOKENEXP", respData.ExpiresAt, ttl, ttl < 2*time.Minute, time.Now())
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)
@ -156,3 +164,72 @@ func (r *GigaChatClient) updateToken(ctx context.Context) error {
return nil
}
func (r *GigaChatClient) getBalance(ctx context.Context) (int, error) {
var respData struct {
Balance []struct {
Usage string `json:"usage"`
Value int `json:"value"`
} `json:"balance"`
}
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 0, err
}
resp, err := r.client.R().
SetHeader("Authorization", "Bearer "+token).
SetResult(&respData).
Get(r.baseURL + "/balance")
if err != nil {
return 0, fmt.Errorf("failed to fetch balance: %w", err)
}
if resp.IsError() {
return 0, fmt.Errorf("balance request failed: %s", resp.Status())
}
// прверяем то что используем для переформулирования
for _, item := range respData.Balance {
if item.Usage == "GigaChat-Max" {
return item.Value, nil
}
}
return 0, errors.New("no used models found")
}
func (r *GigaChatClient) MonitorTokenBalance(ctx context.Context) {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
alert := false // чтоб не спамить каждые 5 минут
for {
select {
case <-ticker.C:
balance, err := r.getBalance(ctx)
if err != nil {
r.logger.Error("failed to get GigaChat token", zap.Error(err))
continue
}
if balance < 500_000 && !alert {
msg := fmt.Sprintf("Остаток токенов в GigaChat упал ниже 500000.\nТекущий баланс: %d токенов.", balance)
if err := r.tgSender.SendMessage(r.tgChatID, msg); err != nil {
r.logger.Error("failed to send Telegram alert", zap.Error(err))
} else {
alert = true
}
}
if balance >= 500_000 && alert {
alert = false
}
case <-ctx.Done():
return
}
}
}

@ -1,17 +1,43 @@
package initialize
import (
"context"
"crypto/tls"
"gitea.pena/PenaSide/customer/pkg/customer_clients"
"gitea.pena/SQuiz/common/clients"
"gitea.pena/SQuiz/worker/internal/clients/gigachat"
"gitea.pena/SQuiz/worker/internal/senders"
"github.com/go-redis/redis/v8"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
)
type Clients struct {
MailClient *clients.SmtpClient
CustomerClient *customer_clients.CustomersClient
GigaChatClient *gigachat.GigaChatClient
}
func NewClients(cfg Config, logger *zap.Logger) *Clients {
func NewClients(ctx context.Context, cfg Config, logger *zap.Logger, redisClient *redis.Client) (*Clients, error) {
notifyTgClient, err := senders.NewTgSender(cfg.NotifyTelegramToken)
if err != nil {
return nil, err
}
gigaChatClient, err := gigachat.NewGigaChatClient(ctx, gigachat.Deps{
Logger: logger,
Client: resty.New().SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}),
BaseURL: cfg.GigaChatApiBaseURL,
AuthKey: cfg.GigaChatApiAuthKey,
RedisClient: redisClient,
TgSender: notifyTgClient,
TgChatID: cfg.NotifyChannelID,
})
if err != nil {
return nil, err
}
return &Clients{
MailClient: clients.NewSmtpClient(clients.Deps{
SmtpSender: cfg.Sender,
@ -22,5 +48,6 @@ func NewClients(cfg Config, logger *zap.Logger) *Clients {
Logger: logger,
CustomerServiceHost: cfg.CustomerMicroserviceRPCURL,
}),
}
GigaChatClient: gigaChatClient,
}, nil
}

@ -29,6 +29,9 @@ type Config struct {
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"`
NotifyTelegramToken string `env:"NOTIFY_TELEGRAM_TOKEN"`
NotifyChannelID int64 `env:"NOTIFY_CHANNEL_ID"`
}
func LoadConfig() (*Config, error) {

@ -30,7 +30,7 @@ type TemplateData struct {
AllAnswers []model.ResultAnswer
QuestionsMap map[uint64]string
AnswerTime string
QuizID int64
QuizID int64
}
func generateTextFromTemplate(data TemplateData, tpl string) (string, error) {
@ -65,7 +65,7 @@ func RenderImage(content string) template.HTML {
contents := strings.Split(content, "`,`")
var builder strings.Builder
content = strings.ReplaceAll(content, "\n","<br>")
content = strings.ReplaceAll(content, "\n", "<br>")
for i, cnt := range contents {
if i == 0 {
cnt = strings.TrimPrefix(cnt, "`")
@ -79,7 +79,15 @@ func RenderImage(content string) template.HTML {
if err != nil {
return SplitContent(content)
} else {
builder.WriteString(fmt.Sprintf("<td>%s<br><img class=\"image\" style=\"width:100%%; max-width:250px; max-height:250px\" src=\"%s\"/></td>", res.Description, strings.Replace(res.Image, "http", "https", 1)))
imgURL := res.Image
if strings.HasPrefix(imgURL, "http://") {
imgURL = strings.Replace(imgURL, "http://", "https://", 1)
}
builder.WriteString(
fmt.Sprintf(`<td style="color:#9a9aaf;font-size:20px;font-style:normal;font-weight:400;line-height:normal">
%s<br> <img class="image" style="width:100%%; max-width:250px; max-height:250px;" src="%s" alt="%s"/></td>`,
res.Description, imgURL, res.Description))
}
}

@ -49,3 +49,11 @@ func (tg *TgSender) SendLead(data LeadData) error {
func (tg *TgSender) Name() string {
return "telegram"
}
func (tg *TgSender) SendMessage(chatID int64, msg string) error {
_, err := tg.bot.Send(telebot.ChatID(chatID), msg)
if err != nil {
return err
}
return nil
}

@ -6,6 +6,7 @@ import (
"fmt"
"gitea.pena/SQuiz/common/model"
"gitea.pena/SQuiz/worker/internal/clients/gigachat"
"gitea.pena/SQuiz/worker/internal/senders"
"github.com/go-redis/redis/v8"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
@ -23,18 +24,27 @@ func TestGigachat(t *testing.T) {
DB: 2,
})
tgSender, err := senders.NewTgSender("6712573453:AAFqTOsgwe_j48ZQ1GzWKQDT5Nwr-SAWjz8")
if err != nil {
panic(err)
}
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==",
AuthKey: "Y2MzZWUxZDMtZGE5MC00ZTFjLWI5YzItM2ViMTZmMDM0YTkwOmY1NTlkOGM3LWUyNmQtNGUwMC1hODE0LTJlYjQ5NDA5ODdjMQ==",
RedisClient: redisClient,
TgSender: tgSender,
TgChatID: -1002217604546,
})
if err != nil {
panic(err)
}
go gigaChatClient.TokenResearch(ctx)
go gigaChatClient.MonitorTokenBalance(ctx)
result, err := gigaChatClient.SendMsg(ctx, model.GigaChatAudience{
Sex: 1,

@ -62,10 +62,10 @@ func TestProcessMessageToSMTP(t *testing.T) {
Messenger: "test_messenger",
},
AllAnswers: []model.ResultAnswer{
{AnswerID: 1, QuestionID: 1, Content: "https://www.google.com/search?sca_esv=c51a80de1a7d45f0&sxsrf=ACQVn08xG-a0eH1Vds246-fONoSvvjzVMw:1707762485524&q=ku,n&tbm=isch&source=lnms&sa=X&ved=2ahUKEwi7ub2Ct6aEAxVVb_UHHQIQBVoQ0pQJegQIDRAB&biw=1536&bih=703&dpr=1.25#imgrc=0PWwTuuH2uBQ3M|html", CreatedAt: time.Now()},
{AnswerID: 2, QuestionID: 2, Content: "From a friend", CreatedAt: time.Now()},
{AnswerID: 1, QuestionID: 1, Content: "`{\"Image\":\"https://s3.timeweb.cloud/3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b/squizimages/e0927ded-5c4c-4d45-a5ba-c2e938362ffa/co0sejfg4n3c73d5umd0\",\"Description\":\"Да\"}`", CreatedAt: time.Now()},
{AnswerID: 2, QuestionID: 2, Content: "`ыв`,`вв`", CreatedAt: time.Now()},
{AnswerID: 3, QuestionID: 3, Content: "From a friend", CreatedAt: time.Now()},
{AnswerID: 4, QuestionID: 4, Content: `{"Image":"https://letsenhance.io/static/8f5e523ee6b2479e26ecc91b9c25261e/1015f/MainAfter.jpg","Description":"Gekon"}`, CreatedAt: time.Now()},
{AnswerID: 4, QuestionID: 4, Content: `{"Image":"static.tildacdn.com/tild6335-6331-4539-a664-376366653534/_problembocom.jpg","Description":"Gekon"}`, CreatedAt: time.Now()},
},
QuestionsMap: map[uint64]string{
1: "?",