diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6043251..d79d3e9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,11 +5,17 @@ include: file: "/templates/docker/deploy-template.gitlab-ci.yml" - project: "devops/pena-continuous-integration" file: "/templates/docker/golint.gitlab-ci.yml" + - project: "devops/pena-continuous-integration" + file: "/templates/docker/service-discovery.gitlab-ci.yml" stages: - lint - build - deploy + - service-discovery + +lint: + extends: .golint_template lint: extends: .golint_template @@ -42,3 +48,6 @@ deploy-prod: - if: "$CI_COMMIT_BRANCH == $PRODUCTION_BRANCH" after_script: - ls + +service-discovery: + extends: .sd_artefacts_template diff --git a/app/app.go b/app/app.go new file mode 100644 index 0000000..70aa0a8 --- /dev/null +++ b/app/app.go @@ -0,0 +1,259 @@ +package app + +import ( + "context" + "errors" + "fmt" + "github.com/go-redis/redis/v8" + "github.com/gofiber/fiber/v2" + "github.com/skeris/appInit" + "github.com/themakers/hlog" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "penahub.gitlab.yandexcloud.net/backend/penahub_common/log_mw" + "penahub.gitlab.yandexcloud.net/backend/penahub_common/privilege" + "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal" + "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/healthchecks" + "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware" + "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" + "penahub.gitlab.yandexcloud.net/backend/quiz/core/brokers" + "penahub.gitlab.yandexcloud.net/backend/quiz/core/clients/auth" + //"penahub.gitlab.yandexcloud.net/backend/quiz/core/clients/telegram" + "penahub.gitlab.yandexcloud.net/backend/quiz/core/initialize" + "penahub.gitlab.yandexcloud.net/backend/quiz/core/models" + "penahub.gitlab.yandexcloud.net/backend/quiz/core/server" + "penahub.gitlab.yandexcloud.net/backend/quiz/core/service" + "penahub.gitlab.yandexcloud.net/backend/quiz/core/tools" + //"penahub.gitlab.yandexcloud.net/backend/quiz/core/workers" + "penahub.gitlab.yandexcloud.net/external/trashlog/wrappers/zaptrashlog" + "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 { + LoggerProdMode bool `env:"IS_PROD_LOG" default:"false"` + IsProd bool `env:"IS_PROD" default:"false"` + NumberPort string `env:"PORT" default:"1488"` + CrtFile string `env:"CRT" default:"server.crt"` + KeyFile string `env:"KEY" default:"server.key"` + PostgresCredentials string `env:"PG_CRED" default:"host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"` + HubAdminUrl string `env:"HUB_ADMIN_URL" default:"http://localhost:8001/"` + ServiceName string `env:"SERVICE_NAME" default:"squiz"` + AuthServiceURL string `env:"AUTH_URL" default:"http://localhost:8000/"` + GrpcHost string `env:"GRPC_HOST" default:"localhost"` + GrpcPort string `env:"GRPC_PORT" default:"9000"` + KafkaBrokers string `env:"KAFKA_BROKERS" default:"localhost:9092"` + KafkaTopic string `env:"KAFKA_TOPIC" default:"test-topic"` + KafkaGroup string `env:"KAFKA_GROUP" default:"mailnotifier"` + TrashLogHost string `env:"TRASH_LOG_HOST" default:"localhost:7113"` + ModuleLogger string `env:"MODULE_LOGGER" default:"core-local"` + ClickHouseCred string `env:"CLICK_HOUSE_CRED" default:"tcp://10.8.0.15:9000/default?sslmode=disable"` + RedisHost string `env:"REDIS_HOST" default:"localhost:6379"` + RedisPassword string `env:"REDIS_PASSWORD" default:"admin"` + RedisDB uint64 `env:"REDIS_DB" default:"2"` + S3Prefix string `env:"S3_PREFIX"` +} + +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), + ) + + clickHouseLogger, err := zaptrashlog.NewCore(ctx, zap.InfoLevel, options.TrashLogHost, ver.Release, ver.Commit, time.Now().Unix()) + if err != nil { + panic(err) + } + + loggerForHlog := zapLogger.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core { + return zapcore.NewTee(core, clickHouseLogger) + })) + + loggerHlog := hlog.New(loggerForHlog).Module(options.ModuleLogger) + loggerHlog.With(models.AllFields{}) + loggerHlog.Emit(InfoSvcStarted{}) + + authClient := auth.NewAuthClient(options.AuthServiceURL) + + pgdal, err := dal.New(ctx, options.PostgresCredentials, nil) + if err != nil { + fmt.Println("NEW", err) + return nil, err + } + + chDal, err := dal.NewClickHouseDAL(ctx, options.ClickHouseCred) + if err != nil { + fmt.Println("failed init clickhouse", err) + return nil, err + } + + kafkaClient, err := initialize.KafkaInit(ctx, initialize.KafkaDeps{ + KafkaGroup: options.KafkaGroup, + KafkaBrokers: options.KafkaBrokers, + KafkaTopic: options.KafkaTopic, + }) + if err != nil { + return nil, err + } + + producer := brokers.NewProducer(brokers.ProducerDeps{ + KafkaClient: kafkaClient, + Logger: zapLogger, + }) + + redisClient := redis.NewClient(&redis.Options{ + Addr: options.RedisHost, + Password: options.RedisPassword, + DB: int(options.RedisDB), + }) + err = redisClient.Ping(ctx).Err() + if err != nil { + panic(fmt.Sprintf("error ping to redis db %v", err)) + } + + clientData := privilege.Client{ + URL: options.HubAdminUrl, + ServiceName: options.ServiceName, + Privileges: model.Privileges, + } + fiberClient := &fiber.Client{} + privilegeController := privilege.NewPrivilege(clientData, fiberClient) + go tools.PublishPrivilege(privilegeController, 10, 5*time.Minute) + + // tgClient, err := telegram.NewTelegramClient(ctx, pgdal) + // if err != nil { + // panic(fmt.Sprintf("failed init tg clietns: %v", err)) + // } + // + // tgWC := workers.NewTgListenerWC(workers.Deps{ + // BotID: int64(6712573453), // todo убрать + // Redis: redisClient, + // Dal: pgdal, + // TgClient: tgClient, + // }) + // + // go tgWC.Start(ctx) + + // todo подумать над реализацией всего а то пока мне кажется что немного каша получается такой предикт что через некоторое время + // сложно будет разобраться что есть где + grpcControllers := initialize.InitRpcControllers(pgdal) + grpc, err := server.NewGRPC(zapLogger) + if err != nil { + fmt.Println("error:", err) + panic("err init grpc server") + } + grpc.Register(grpcControllers) + go grpc.Run(server.DepsGrpcRun{ + Host: options.GrpcHost, + Port: options.GrpcPort, + }) + + app := fiber.New() + app.Use(middleware.JWTAuth()) + app.Use(log_mw.ContextLogger(loggerHlog)) + app.Get("/liveness", healthchecks.Liveness) + app.Get("/readiness", healthchecks.Readiness(&workerErr)) //todo parametrized readiness. should discuss ready reason + + svc := service.New(service.Deps{ + Dal: pgdal, + AuthClient: authClient, + Producer: producer, + ServiceName: options.ServiceName, + ChDAL: chDal, + // TelegramClient: tgClient, + RedisClient: redisClient, + S3Prefix: options.S3Prefix, + }) + + svc.Register(app) + + loggerHlog.Emit(InfoSvcReady{}) + + go func() { + defer func() { + if pgdal != nil { + pgdal.Close() + } + if chDal != nil { + if derr := chDal.Close(ctx); derr != nil { + fmt.Printf("error closing clickhouse: %v", derr) + } + } + err := grpc.Stop(ctx) + err = app.Shutdown() + loggerHlog.Emit(InfoSvcShutdown{Signal: err.Error()}) + }() + + if options.IsProd { + if err := app.ListenTLS(fmt.Sprintf(":%s", options.NumberPort), options.CrtFile, options.KeyFile); err != nil { + loggerHlog.Emit(ErrorCanNotServe{ + Err: err, + }) + errChan <- err + } + } else { + if err := app.Listen(fmt.Sprintf(":%s", options.NumberPort)); err != nil { + loggerHlog.Emit(ErrorCanNotServe{ + Err: err, + }) + errChan <- err + } + } + + errChan <- nil + }() + // todo implement helper func for service app type. such as server preparing, logger preparing, healthchecks and etc. + return &App{ + logger: zapLogger, + err: errChan, + }, err +} diff --git a/deployments/staging/docker-compose.yaml b/deployments/staging/docker-compose.yaml index e0928cd..874f5bc 100644 --- a/deployments/staging/docker-compose.yaml +++ b/deployments/staging/docker-compose.yaml @@ -1,10 +1,12 @@ version: "3" services: core: - hostname: squiz-core - container_name: squiz-core + hostname: squiz + container_name: squiz image: $CI_REGISTRY_IMAGE/staging-core:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID tty: true + labels: + com.pena.allowed_headers: content-type,authorization,device,browser,os,devicetype,response-type environment: HUB_ADMIN_URL: 'http://10.8.0.6:59303' IS_PROD_LOG: 'false' @@ -22,6 +24,9 @@ services: TRASH_LOG_HOST: "10.8.0.15:7113" MODULE_LOGGER: "quiz-core-staging" CLICK_HOUSE_CRED: "clickhouse://10.8.0.15:9000/default?sslmode=disable" + REDIS_HOST: '10.8.0.5:6379' + REDIS_PASSWORD: 'Redalert2' + REDIS_DB: 2 ports: - 10.8.0.5:1488:1488 - 10.8.0.5:9000:9000 diff --git a/go.mod b/go.mod index 309059e..1ed5126 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module penahub.gitlab.yandexcloud.net/backend/quiz/core -go 1.22.4 +go 1.22.0 require ( github.com/go-redis/redis/v8 v8.11.5 @@ -10,19 +10,18 @@ require ( github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 github.com/pioz/faker v1.7.3 - github.com/rs/xid v1.5.0 + github.com/rs/xid v1.6.0 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.16.1 github.com/xuri/excelize/v2 v2.8.1 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.64.0 + google.golang.org/grpc v1.66.0 google.golang.org/protobuf v1.34.2 penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240607202348-efe5f2bf3e8c - penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20241025130405-8ab347d96f1f penahub.gitlab.yandexcloud.net/backend/quiz/worker.git v0.0.0-20240421230341-0e086fcbb990 - penahub.gitlab.yandexcloud.net/backend/tdlib v0.0.0-20240701075856-1731684c936f + penahub.gitlab.yandexcloud.net/devops/linters/golang.git v0.0.0-20240829220549-d35409b619a3 penahub.gitlab.yandexcloud.net/external/trashlog v0.1.6-0.20240827173635-78ce9878c387 ) @@ -59,13 +58,13 @@ require ( github.com/valyala/tcplisten v1.0.0 // indirect github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect - go.etcd.io/bbolt v1.3.6 // indirect + go.etcd.io/bbolt v1.3.11 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/net v0.26.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index f820251..ca9557a 100644 --- a/go.sum +++ b/go.sum @@ -128,8 +128,8 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 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= @@ -165,8 +165,8 @@ github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNh github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= @@ -185,8 +185,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= @@ -205,30 +205,31 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -250,15 +251,15 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 h1:9Xyg6I9IWQZhRVfCWjKK+l6kI0jHcPesVlMnT//aHNo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -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/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= +google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= 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= @@ -288,11 +289,11 @@ penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240607202348-efe5 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-20240711133242-0b8534fae5b2 h1:0t6pQHJvA3jMeBB3FPUmA+hw8rWlksTah8KJEtf2KD8= penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240711133242-0b8534fae5b2/go.mod h1:uOuosXduBzd2WbLH6TDZO7ME7ZextulA662oZ6OsoB0= -penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20241025130405-8ab347d96f1f h1:HMXxbIkNmFeQTFI/MlsDBIRUTu8MTbn3i2PVcpQbXEI= -penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20241025130405-8ab347d96f1f/go.mod h1:uOuosXduBzd2WbLH6TDZO7ME7ZextulA662oZ6OsoB0= +penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240919124706-53cacd30903a h1:loD4hoCkx1ZTqw0mg/Br4RT2GI1vT2WvEh0I/j+DPo8= +penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240919124706-53cacd30903a/go.mod h1:uOuosXduBzd2WbLH6TDZO7ME7ZextulA662oZ6OsoB0= penahub.gitlab.yandexcloud.net/backend/quiz/worker.git v0.0.0-20240421230341-0e086fcbb990 h1:jiO8GWO+3sCnDAV8/NAV8tQIUwae/I6/xiDilW7zf0o= penahub.gitlab.yandexcloud.net/backend/quiz/worker.git v0.0.0-20240421230341-0e086fcbb990/go.mod h1:zswBuTwmEsFHBVRu1nkG3/Fwylk5Vcm8OUm9iWxccSE= -penahub.gitlab.yandexcloud.net/backend/tdlib v0.0.0-20240701075856-1731684c936f h1:Qli89wgu0T7nG4VECXZOZ40fjE/hVVfxF3hTaSYS008= -penahub.gitlab.yandexcloud.net/backend/tdlib v0.0.0-20240701075856-1731684c936f/go.mod h1:AkE19hcbDwB7hoEASwImm7rUI+cK/8jMVJaTvMK4F+c= +penahub.gitlab.yandexcloud.net/devops/linters/golang.git v0.0.0-20240829220549-d35409b619a3 h1:sf6e2mp582L3i/FMDd2q6QuWm1njRXzYpIX0SipsvM4= +penahub.gitlab.yandexcloud.net/devops/linters/golang.git v0.0.0-20240829220549-d35409b619a3/go.mod h1:i7M72RIpkSjcQtHID6KKj9RT/EYZ1rxS6tIPKWa/BSY= penahub.gitlab.yandexcloud.net/external/trashlog v0.1.6-0.20240827173635-78ce9878c387 h1:G+GIhkkvUsM9No2rf2D4kvQ2ExTw9KxlA8vsSnC0ywU= penahub.gitlab.yandexcloud.net/external/trashlog v0.1.6-0.20240827173635-78ce9878c387/go.mod h1:30nezjpGpZuNThbQOCULIfa79RoJ5sray593jhfVP/Q= diff --git a/internal/clients/telegram/tg.go b/internal/clients/telegram/tg.go index 7f43d23..9d62e29 100644 --- a/internal/clients/telegram/tg.go +++ b/internal/clients/telegram/tg.go @@ -1,246 +1,246 @@ package telegram - -import ( - "context" - "errors" - "fmt" - "path/filepath" - "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/tdlib/client" - "sync" - "time" -) - -type TelegramClient struct { - repo *dal.DAL - TgClients map[int64]*client.Client - WaitingClients map[string]WaitingClient - mu sync.Mutex -} - -type WaitingClient struct { - PreviousReq AuthTgUserReq - Authorizer *client.ClientAuthorizer -} - -func NewTelegramClient(ctx context.Context, repo *dal.DAL) (*TelegramClient, error) { - tgClient := &TelegramClient{ - repo: repo, - TgClients: make(map[int64]*client.Client), - WaitingClients: make(map[string]WaitingClient), - } - - allTgAccounts, err := repo.TgRepo.GetAllTgAccounts(ctx) - if err != nil { - if errors.Is(err, pj_errors.ErrNotFound) { - return tgClient, nil - } - return nil, err - } - - for _, account := range allTgAccounts { - if account.Status == model.ActiveTg { - authorizer := client.ClientAuthorizerr() - authorizer.TdlibParameters <- &client.SetTdlibParametersRequest{ - UseTestDc: false, - DatabaseDirectory: filepath.Join(".tdlib", "database"), - FilesDirectory: filepath.Join(".tdlib", "files"), - UseFileDatabase: true, - UseChatInfoDatabase: true, - UseMessageDatabase: true, - UseSecretChats: true, - ApiId: account.ApiID, - ApiHash: account.ApiHash, - SystemLanguageCode: "en", - DeviceModel: "Server", - SystemVersion: "1.0.0", - ApplicationVersion: "1.0.0", - } - - _, err := client.SetLogVerbosityLevel(&client.SetLogVerbosityLevelRequest{ - NewVerbosityLevel: 1, - }) - if err != nil { - return nil, err - } - - var tdlibClient *client.Client - var goErr error - go func() { - tdlibClient, goErr = client.NewClient(authorizer) - if goErr != nil { - fmt.Println("new client failed", err) - return - } - fmt.Println("i am down") - }() - if goErr != nil { - return nil, goErr - } - - for { - state, ok := <-authorizer.State - if !ok { - break - } - fmt.Println("currnet state:", state) - switch state.AuthorizationStateType() { - case client.TypeAuthorizationStateWaitPhoneNumber: - authorizer.PhoneNumber <- account.PhoneNumber - case client.TypeAuthorizationStateWaitCode: - err := tgClient.repo.TgRepo.UpdateStatusTg(ctx, account.ID, model.InactiveTg) - if err != nil { - return nil, err - } - case client.TypeAuthorizationStateLoggingOut, client.TypeAuthorizationStateClosing, client.TypeAuthorizationStateClosed: - err := tgClient.repo.TgRepo.UpdateStatusTg(ctx, account.ID, model.InactiveTg) - if err != nil { - return nil, err - } - case client.TypeAuthorizationStateReady: - // костыль так как в либе тож костыль стоит пока там ьд обновиться будет ниловый всегда клиент - time.Sleep(3 * time.Second) - me, err := tdlibClient.GetMe() - if err != nil { - return nil, err - } - fmt.Printf("Me: %s %s [%v]", me.FirstName, me.LastName, me.Usernames) - tgClient.mu.Lock() - tgClient.TgClients[account.ID] = tdlibClient - tgClient.mu.Unlock() - break - case client.TypeAuthorizationStateWaitPassword: - authorizer.Password <- account.Password - } - } - } - } - return tgClient, nil -} - -type AuthTgUserReq struct { - ApiID int32 `json:"api_id"` - ApiHash string `json:"api_hash"` - PhoneNumber string `json:"phone_number"` - Password string `json:"password"` -} - -func (tg *TelegramClient) AddedToMap(data WaitingClient, id string) { - fmt.Println("AddedToMap") - tg.mu.Lock() - defer tg.mu.Unlock() - tg.WaitingClients[id] = data -} - -func (tg *TelegramClient) GetFromMap(id string) (WaitingClient, bool) { - fmt.Println("GetFromMap") - tg.mu.Lock() - defer tg.mu.Unlock() - if data, ok := tg.WaitingClients[id]; ok { - delete(tg.WaitingClients, id) - return data, true - } - return WaitingClient{}, false -} - -func (tg *TelegramClient) SaveTgAccount(appID int32, appHash string, tdLibClient *client.Client) { - account, err := tg.repo.TgRepo.SearchIDByAppIDanAppHash(context.Background(), appID, appHash) - if err != nil { - fmt.Println("err SaveTgAccount", err) - return - } - if account.Status == model.ActiveTg { - tg.mu.Lock() - defer tg.mu.Unlock() - tg.TgClients[account.ID] = tdLibClient - } -} - -func (tg *TelegramClient) CreateChannel(channelName string, botID int64) (string, int64, error) { - tg.mu.Lock() - defer tg.mu.Unlock() - if len(tg.TgClients) == 0 { - return "", 0, errors.New("no active Telegram clients") - } - var lastError error - var inviteLink string - var channelId int64 - for _, activeClient := range tg.TgClients { - // todo пока не понимаю это какой то рандом? в один день бот норм находится в другой уже не находится хотя абсолютно с точки зрения тг кода этой функции и бота не менялось - _, err := activeClient.GetUser(&client.GetUserRequest{ - UserId: botID, - }) - if err != nil { - lastError = fmt.Errorf("not found this bot, make privacy off: %v", err) - continue - } - - // todo нужно поймать ошибку, при которой либо бан либо медленный редим включается для того чтобы прервать - // исполнение клиента текущего аккаунта и дать задачу следующему пока поймал 1 раз и не запомнил больше не получается - channel, err := activeClient.CreateNewSupergroupChat(&client.CreateNewSupergroupChatRequest{ - Title: channelName, - IsChannel: true, - Description: "private channel", - }) - if err != nil { - lastError = fmt.Errorf("failed to create channel: %s", err.Error()) - continue - } - - _, err = activeClient.SetChatMemberStatus(&client.SetChatMemberStatusRequest{ - ChatId: channel.Id, - MemberId: &client.MessageSenderUser{UserId: botID}, - Status: &client.ChatMemberStatusAdministrator{ - CustomTitle: "bot", - Rights: &client.ChatAdministratorRights{ - CanManageChat: true, - CanChangeInfo: true, - CanPostMessages: true, - CanEditMessages: true, - CanDeleteMessages: true, - CanInviteUsers: true, - CanRestrictMembers: true, - CanPinMessages: true, - CanManageTopics: true, - CanPromoteMembers: true, - CanManageVideoChats: true, - CanPostStories: true, - CanEditStories: true, - CanDeleteStories: true, - }, - }, - }) - if err != nil { - lastError = fmt.Errorf("failed to make bot admin: %s", err.Error()) - continue - } - - inviteLinkResp, err := activeClient.CreateChatInviteLink(&client.CreateChatInviteLinkRequest{ - ChatId: channel.Id, - Name: channelName, - ExpirationDate: 0, - MemberLimit: 0, - CreatesJoinRequest: false, - }) - if err != nil { - lastError = fmt.Errorf("failed to get invite link: %s", err.Error()) - continue - } - - _, err = activeClient.LeaveChat(&client.LeaveChatRequest{ - ChatId: channel.Id, - }) - if err != nil { - lastError = fmt.Errorf("failed to leave the channel: %s", err.Error()) - continue - } - - inviteLink = inviteLinkResp.InviteLink - channelId = channel.Id - return inviteLink, channelId, nil - } - - return "", 0, lastError -} +// +// import ( +// "context" +// "errors" +// "fmt" +// "path/filepath" +// "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/tdlib/client" +// "sync" +// "time" +// ) +// +// type TelegramClient struct { +// repo *dal.DAL +// TgClients map[int64]*client.Client +// WaitingClients map[string]WaitingClient +// mu sync.Mutex +// } +// +// type WaitingClient struct { +// PreviousReq AuthTgUserReq +// Authorizer *client.ClientAuthorizer +// } +// +// func NewTelegramClient(ctx context.Context, repo *dal.DAL) (*TelegramClient, error) { +// tgClient := &TelegramClient{ +// repo: repo, +// TgClients: make(map[int64]*client.Client), +// WaitingClients: make(map[string]WaitingClient), +// } +// +// allTgAccounts, err := repo.TgRepo.GetAllTgAccounts(ctx) +// if err != nil { +// if errors.Is(err, pj_errors.ErrNotFound) { +// return tgClient, nil +// } +// return nil, err +// } +// +// for _, account := range allTgAccounts { +// if account.Status == model.ActiveTg { +// authorizer := client.ClientAuthorizerr() +// authorizer.TdlibParameters <- &client.SetTdlibParametersRequest{ +// UseTestDc: false, +// DatabaseDirectory: filepath.Join(".tdlib", "database"), +// FilesDirectory: filepath.Join(".tdlib", "files"), +// UseFileDatabase: true, +// UseChatInfoDatabase: true, +// UseMessageDatabase: true, +// UseSecretChats: true, +// ApiId: account.ApiID, +// ApiHash: account.ApiHash, +// SystemLanguageCode: "en", +// DeviceModel: "Server", +// SystemVersion: "1.0.0", +// ApplicationVersion: "1.0.0", +// } +// +// _, err := client.SetLogVerbosityLevel(&client.SetLogVerbosityLevelRequest{ +// NewVerbosityLevel: 1, +// }) +// if err != nil { +// return nil, err +// } +// +// var tdlibClient *client.Client +// var goErr error +// go func() { +// tdlibClient, goErr = client.NewClient(authorizer) +// if goErr != nil { +// fmt.Println("new client failed", err) +// return +// } +// fmt.Println("i am down") +// }() +// if goErr != nil { +// return nil, goErr +// } +// +// for { +// state, ok := <-authorizer.State +// if !ok { +// break +// } +// fmt.Println("currnet state:", state) +// switch state.AuthorizationStateType() { +// case client.TypeAuthorizationStateWaitPhoneNumber: +// authorizer.PhoneNumber <- account.PhoneNumber +// case client.TypeAuthorizationStateWaitCode: +// err := tgClient.repo.TgRepo.UpdateStatusTg(ctx, account.ID, model.InactiveTg) +// if err != nil { +// return nil, err +// } +// case client.TypeAuthorizationStateLoggingOut, client.TypeAuthorizationStateClosing, client.TypeAuthorizationStateClosed: +// err := tgClient.repo.TgRepo.UpdateStatusTg(ctx, account.ID, model.InactiveTg) +// if err != nil { +// return nil, err +// } +// case client.TypeAuthorizationStateReady: +// // костыль так как в либе тож костыль стоит пока там ьд обновиться будет ниловый всегда клиент +// time.Sleep(3 * time.Second) +// me, err := tdlibClient.GetMe() +// if err != nil { +// return nil, err +// } +// fmt.Printf("Me: %s %s [%v]", me.FirstName, me.LastName, me.Usernames) +// tgClient.mu.Lock() +// tgClient.TgClients[account.ID] = tdlibClient +// tgClient.mu.Unlock() +// break +// case client.TypeAuthorizationStateWaitPassword: +// authorizer.Password <- account.Password +// } +// } +// } +// } +// return tgClient, nil +// } +// +// type AuthTgUserReq struct { +// ApiID int32 `json:"api_id"` +// ApiHash string `json:"api_hash"` +// PhoneNumber string `json:"phone_number"` +// Password string `json:"password"` +// } +// +// func (tg *TelegramClient) AddedToMap(data WaitingClient, id string) { +// fmt.Println("AddedToMap") +// tg.mu.Lock() +// defer tg.mu.Unlock() +// tg.WaitingClients[id] = data +// } +// +// func (tg *TelegramClient) GetFromMap(id string) (WaitingClient, bool) { +// fmt.Println("GetFromMap") +// tg.mu.Lock() +// defer tg.mu.Unlock() +// if data, ok := tg.WaitingClients[id]; ok { +// delete(tg.WaitingClients, id) +// return data, true +// } +// return WaitingClient{}, false +// } +// +// func (tg *TelegramClient) SaveTgAccount(appID int32, appHash string, tdLibClient *client.Client) { +// account, err := tg.repo.TgRepo.SearchIDByAppIDanAppHash(context.Background(), appID, appHash) +// if err != nil { +// fmt.Println("err SaveTgAccount", err) +// return +// } +// if account.Status == model.ActiveTg { +// tg.mu.Lock() +// defer tg.mu.Unlock() +// tg.TgClients[account.ID] = tdLibClient +// } +// } +// +// func (tg *TelegramClient) CreateChannel(channelName string, botID int64) (string, int64, error) { +// tg.mu.Lock() +// defer tg.mu.Unlock() +// if len(tg.TgClients) == 0 { +// return "", 0, errors.New("no active Telegram clients") +// } +// var lastError error +// var inviteLink string +// var channelId int64 +// for _, activeClient := range tg.TgClients { +// // todo пока не понимаю это какой то рандом? в один день бот норм находится в другой уже не находится хотя абсолютно с точки зрения тг кода этой функции и бота не менялось +// _, err := activeClient.GetUser(&client.GetUserRequest{ +// UserId: botID, +// }) +// if err != nil { +// lastError = fmt.Errorf("not found this bot, make privacy off: %v", err) +// continue +// } +// +// // todo нужно поймать ошибку, при которой либо бан либо медленный редим включается для того чтобы прервать +// // исполнение клиента текущего аккаунта и дать задачу следующему пока поймал 1 раз и не запомнил больше не получается +// channel, err := activeClient.CreateNewSupergroupChat(&client.CreateNewSupergroupChatRequest{ +// Title: channelName, +// IsChannel: true, +// Description: "private channel", +// }) +// if err != nil { +// lastError = fmt.Errorf("failed to create channel: %s", err.Error()) +// continue +// } +// +// _, err = activeClient.SetChatMemberStatus(&client.SetChatMemberStatusRequest{ +// ChatId: channel.Id, +// MemberId: &client.MessageSenderUser{UserId: botID}, +// Status: &client.ChatMemberStatusAdministrator{ +// CustomTitle: "bot", +// Rights: &client.ChatAdministratorRights{ +// CanManageChat: true, +// CanChangeInfo: true, +// CanPostMessages: true, +// CanEditMessages: true, +// CanDeleteMessages: true, +// CanInviteUsers: true, +// CanRestrictMembers: true, +// CanPinMessages: true, +// CanManageTopics: true, +// CanPromoteMembers: true, +// CanManageVideoChats: true, +// CanPostStories: true, +// CanEditStories: true, +// CanDeleteStories: true, +// }, +// }, +// }) +// if err != nil { +// lastError = fmt.Errorf("failed to make bot admin: %s", err.Error()) +// continue +// } +// +// inviteLinkResp, err := activeClient.CreateChatInviteLink(&client.CreateChatInviteLinkRequest{ +// ChatId: channel.Id, +// Name: channelName, +// ExpirationDate: 0, +// MemberLimit: 0, +// CreatesJoinRequest: false, +// }) +// if err != nil { +// lastError = fmt.Errorf("failed to get invite link: %s", err.Error()) +// continue +// } +// +// _, err = activeClient.LeaveChat(&client.LeaveChatRequest{ +// ChatId: channel.Id, +// }) +// if err != nil { +// lastError = fmt.Errorf("failed to leave the channel: %s", err.Error()) +// continue +// } +// +// inviteLink = inviteLinkResp.InviteLink +// channelId = channel.Id +// return inviteLink, channelId, nil +// } +// +// return "", 0, lastError +// } diff --git a/internal/controllers/http_controllers/telegram/telegram.go b/internal/controllers/http_controllers/telegram/telegram.go index 1b09e91..d0989d0 100644 --- a/internal/controllers/http_controllers/telegram/telegram.go +++ b/internal/controllers/http_controllers/telegram/telegram.go @@ -2,15 +2,14 @@ package telegram import ( "errors" - "fmt" + //"fmt" "github.com/gofiber/fiber/v2" - "github.com/rs/xid" - "path/filepath" - "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal" - "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" + // "github.com/rs/xid" + //"path/filepath" + // "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/core/internal/clients/telegram" - "penahub.gitlab.yandexcloud.net/backend/tdlib/client" + // "penahub.gitlab.yandexcloud.net/backend/quiz/core/clients/telegram" + // "penahub.gitlab.yandexcloud.net/backend/tdlib/client" "strconv" ) @@ -49,87 +48,88 @@ func (r *Telegram) GetPoolTgAccounts(ctx *fiber.Ctx) error { return ctx.Status(fiber.StatusOK).JSON(allAccounts) } -func (r *Telegram) AddingTgAccount(ctx *fiber.Ctx) error { - var req telegram.AuthTgUserReq - if err := ctx.BodyParser(&req); err != nil { - return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") - } - if req.ApiID == 0 || req.ApiHash == "" || req.Password == "" || req.PhoneNumber == "" { - return ctx.Status(fiber.StatusBadRequest).SendString("empty required fields") - } - allAccounts, err := r.dal.TgRepo.GetAllTgAccounts(ctx.Context()) - if err != nil && !errors.Is(err, pj_errors.ErrNotFound) { - return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) - } - if !errors.Is(err, pj_errors.ErrNotFound) { - for _, account := range allAccounts { - if account.ApiID == req.ApiID && account.ApiHash == req.ApiHash && account.Status == model.ActiveTg { - return ctx.Status(fiber.StatusConflict).SendString("this account already exist and active") - } - } - } - authorizer := client.ClientAuthorizerr() - authorizer.TdlibParameters <- &client.SetTdlibParametersRequest{ - UseTestDc: false, - DatabaseDirectory: filepath.Join(".tdlib", "database"), - FilesDirectory: filepath.Join(".tdlib", "files"), - UseFileDatabase: true, - UseChatInfoDatabase: true, - UseMessageDatabase: true, - UseSecretChats: true, - ApiId: req.ApiID, - ApiHash: req.ApiHash, - SystemLanguageCode: "en", - DeviceModel: "Server", - SystemVersion: "1.0.0", - ApplicationVersion: "1.0.0", - } - - _, err = client.SetLogVerbosityLevel(&client.SetLogVerbosityLevelRequest{ - NewVerbosityLevel: 1, - }) - if err != nil { - return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) - } - - var tdlibClient *client.Client - // завершается уже в другом контроллере - var goErr error - // todo ужно продумать завершение горутины если код вставлять не пошли - go func() { - tdlibClient, goErr = client.NewClient(authorizer) - if goErr != nil { - fmt.Println("new client failed", err) - return - } - r.telegramClient.SaveTgAccount(req.ApiID, req.ApiHash, tdlibClient) - fmt.Println("i am down") - }() - if goErr != nil { - return ctx.Status(fiber.StatusInternalServerError).SendString(goErr.Error()) - } - - for { - state, ok := <-authorizer.State - if !ok { - return ctx.Status(fiber.StatusOK).SendString("state chan is close auth maybe ok") - } - fmt.Println("currnet state:", state) - switch state.AuthorizationStateType() { - case client.TypeAuthorizationStateWaitPhoneNumber: - authorizer.PhoneNumber <- req.PhoneNumber - case client.TypeAuthorizationStateWaitCode: - signature := xid.New() - r.telegramClient.AddedToMap(telegram.WaitingClient{ - PreviousReq: req, - Authorizer: authorizer, - }, signature.String()) - return ctx.Status(fiber.StatusOK).JSON(fiber.Map{"signature": signature.String()}) - - case client.TypeAuthorizationStateLoggingOut, client.TypeAuthorizationStateClosing, client.TypeAuthorizationStateClosed: - return ctx.Status(fiber.StatusForbidden).SendString(fmt.Sprintf("auth failed, last state is %s", state)) - } - } +func (s *Service) AddingTgAccount(ctx *fiber.Ctx) error { + // var req telegram.AuthTgUserReq + // if err := ctx.BodyParser(&req); err != nil { + // return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data") + // } + // if req.ApiID == 0 || req.ApiHash == "" || req.Password == "" || req.PhoneNumber == "" { + // return ctx.Status(fiber.StatusBadRequest).SendString("empty required fields") + // } + // allAccounts, err := s.dal.TgRepo.GetAllTgAccounts(ctx.Context()) + // if err != nil && !errors.Is(err, pj_errors.ErrNotFound) { + // return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) + // } + // if !errors.Is(err, pj_errors.ErrNotFound) { + // for _, account := range allAccounts { + // if account.ApiID == req.ApiID && account.ApiHash == req.ApiHash && account.Status == model.ActiveTg { + // return ctx.Status(fiber.StatusConflict).SendString("this account already exist and active") + // } + // } + // } + // authorizer := client.ClientAuthorizerr() + // authorizer.TdlibParameters <- &client.SetTdlibParametersRequest{ + // UseTestDc: false, + // DatabaseDirectory: filepath.Join(".tdlib", "database"), + // FilesDirectory: filepath.Join(".tdlib", "files"), + // UseFileDatabase: true, + // UseChatInfoDatabase: true, + // UseMessageDatabase: true, + // UseSecretChats: true, + // ApiId: req.ApiID, + // ApiHash: req.ApiHash, + // SystemLanguageCode: "en", + // DeviceModel: "Server", + // SystemVersion: "1.0.0", + // ApplicationVersion: "1.0.0", + // } + // + // _, err = client.SetLogVerbosityLevel(&client.SetLogVerbosityLevelRequest{ + // NewVerbosityLevel: 1, + // }) + // if err != nil { + // return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) + // } + // + // var tdlibClient *client.Client + // // завершается уже в другом контроллере + // var goErr error + // // todo ужно продумать завершение горутины если код вставлять не пошли + // go func() { + // tdlibClient, goErr = client.NewClient(authorizer) + // if goErr != nil { + // fmt.Println("new client failed", err) + // return + // } + // s.telegramClient.SaveTgAccount(req.ApiID, req.ApiHash, tdlibClient) + // fmt.Println("i am down") + // }() + // if goErr != nil { + // return ctx.Status(fiber.StatusInternalServerError).SendString(goErr.Error()) + // } + // + // for { + // state, ok := <-authorizer.State + // if !ok { + // return ctx.Status(fiber.StatusOK).SendString("state chan is close auth maybe ok") + // } + // fmt.Println("currnet state:", state) + // switch state.AuthorizationStateType() { + // case client.TypeAuthorizationStateWaitPhoneNumber: + // authorizer.PhoneNumber <- req.PhoneNumber + // case client.TypeAuthorizationStateWaitCode: + // signature := xid.New() + // s.telegramClient.AddedToMap(telegram.WaitingClient{ + // PreviousReq: req, + // Authorizer: authorizer, + // }, signature.String()) + // return ctx.Status(fiber.StatusOK).JSON(fiber.Map{"signature": signature.String()}) + // + // case client.TypeAuthorizationStateLoggingOut, client.TypeAuthorizationStateClosing, client.TypeAuthorizationStateClosed: + // return ctx.Status(fiber.StatusForbidden).SendString(fmt.Sprintf("auth failed, last state is %s", state)) + // } + // } + return nil } func (r *Telegram) SettingTgCode(ctx *fiber.Ctx) error { @@ -144,37 +144,38 @@ func (r *Telegram) SettingTgCode(ctx *fiber.Ctx) error { if req.Code == "" || req.Signature == "" { return ctx.Status(fiber.StatusBadRequest).SendString("empty required fields") } - - data, ok := r.telegramClient.GetFromMap(req.Signature) - if !ok { - return ctx.Status(fiber.StatusBadRequest).SendString("Invalid id, don't have data") - } - data.Authorizer.Code <- req.Code - for { - state, ok := <-data.Authorizer.State - if !ok { - return ctx.Status(fiber.StatusNoContent).SendString("state chan is close auth maybe ok") - } - fmt.Println("currnet state:", state) - switch state.AuthorizationStateType() { - case client.TypeAuthorizationStateReady: - id, err := r.dal.TgRepo.CreateTgAccount(ctx.Context(), model.TgAccount{ - ApiID: data.PreviousReq.ApiID, - ApiHash: data.PreviousReq.ApiHash, - PhoneNumber: data.PreviousReq.PhoneNumber, - Status: model.ActiveTg, - Password: data.PreviousReq.Password, - }) - if err != nil { - return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) - } - return ctx.Status(fiber.StatusOK).JSON(fiber.Map{"id": id}) - case client.TypeAuthorizationStateWaitPassword: - data.Authorizer.Password <- data.PreviousReq.Password - case client.TypeAuthorizationStateLoggingOut, client.TypeAuthorizationStateClosing, client.TypeAuthorizationStateClosed: - return ctx.Status(fiber.StatusForbidden).SendString(fmt.Sprintf("auth failed, last state is %s", state)) - } - } + // data, ok := s.telegramClient.GetFromMap(req.Signature) + // if !ok { + // return ctx.Status(fiber.StatusBadRequest).SendString("Invalid id, don't have data") + // } + // data.Authorizer.Code <- req.Code + // for { + // state, ok := <-data.Authorizer.State + // if !ok { + // return ctx.Status(fiber.StatusNoContent).SendString("state chan is close auth maybe ok") + // } + // fmt.Println("currnet state:", state) + // } + return nil + // switch state.AuthorizationStateType() { + // case client.TypeAuthorizationStateReady: + // id, err := s.dal.TgRepo.CreateTgAccount(ctx.Context(), model.TgAccount{ + // ApiID: data.PreviousReq.ApiID, + // ApiHash: data.PreviousReq.ApiHash, + // PhoneNumber: data.PreviousReq.PhoneNumber, + // Status: model.ActiveTg, + // Password: data.PreviousReq.Password, + // }) + // if err != nil { + // return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error()) + // } + // return ctx.Status(fiber.StatusOK).JSON(fiber.Map{"id": id}) + // case client.TypeAuthorizationStateWaitPassword: + // data.Authorizer.Password <- data.PreviousReq.Password + // case client.TypeAuthorizationStateLoggingOut, client.TypeAuthorizationStateClosing, client.TypeAuthorizationStateClosed: + // return ctx.Status(fiber.StatusForbidden).SendString(fmt.Sprintf("auth failed, last state is %s", state)) + // } + // } } func (r *Telegram) DeleteTgAccountByID(ctx *fiber.Ctx) error { diff --git a/internal/tools/tools.go b/internal/tools/tools.go index 9fa57f5..88cbc68 100644 --- a/internal/tools/tools.go +++ b/internal/tools/tools.go @@ -42,6 +42,8 @@ func WriteDataToExcel(buffer io.Writer, questions []model.Question, answers []mo }) headers, mapQueRes := prepareHeaders(questions) + headers = append([]string{"Дата и время"}, headers...) + for col, header := range headers { cell := ToAlphaString(col+1) + "1" if err := file.SetCellValue(sheet, cell, header); err != nil { @@ -107,10 +109,15 @@ func processSession(file *excelize.File, sheet, session, s3Prefix string, respon } }() - if err := file.SetCellValue(sheet, "A"+strconv.Itoa(row), results[session].Content); err != nil { + if err := file.SetCellValue(sheet, "A"+strconv.Itoa(row), results[session].CreatedAt.Format("2006-01-02 15:04:05")); err != nil { fmt.Println(err.Error()) } - count := 2 + + if err := file.SetCellValue(sheet, "B"+strconv.Itoa(row), results[session].Content); err != nil { + fmt.Println(err.Error()) + } + + count := 3 for _, q := range questions { if !q.Deleted && q.Type != model.TypeResult { cell := ToAlphaString(count) + strconv.Itoa(row) diff --git a/service/service.go b/service/service.go new file mode 100644 index 0000000..08c40df --- /dev/null +++ b/service/service.go @@ -0,0 +1,101 @@ +package service + +import ( + "github.com/go-redis/redis/v8" + "github.com/gofiber/fiber/v2" + "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal" + "penahub.gitlab.yandexcloud.net/backend/quiz/core/brokers" + "penahub.gitlab.yandexcloud.net/backend/quiz/core/clients/auth" +// "penahub.gitlab.yandexcloud.net/backend/quiz/core/clients/telegram" +) + +// Service is an entity for http requests handling +type Service struct { + dal *dal.DAL + authClient *auth.AuthClient + producer *brokers.Producer + serviceName string + chDAL *dal.ClickHouseDAL +// telegramClient *telegram.TelegramClient + redisClient *redis.Client + s3Prefix string +} + +type Deps struct { + Dal *dal.DAL + AuthClient *auth.AuthClient + Producer *brokers.Producer + ServiceName string + ChDAL *dal.ClickHouseDAL +// TelegramClient *telegram.TelegramClient + RedisClient *redis.Client + S3Prefix string +} + +func New(deps Deps) *Service { + return &Service{ + dal: deps.Dal, + authClient: deps.AuthClient, + producer: deps.Producer, + serviceName: deps.ServiceName, + chDAL: deps.ChDAL, +// telegramClient: deps.TelegramClient, + redisClient: deps.RedisClient, + s3Prefix: deps.S3Prefix, + } +} + +// Register is a function for add handlers of service to external multiplexer +func (s *Service) Register(app *fiber.App) { + // quiz manipulating handlers + app.Post("/quiz/create", s.CreateQuiz) + app.Post("/quiz/getList", s.GetQuizList) + app.Patch("/quiz/edit", s.UpdateQuiz) + app.Post("/quiz/copy", s.CopyQuiz) + app.Post("/quiz/history", s.GetQuizHistory) + app.Delete("/quiz/delete", s.DeleteQuiz) + app.Patch("/quiz/archive", s.ArchiveQuiz) + app.Post("/quiz/move", s.QuizMove) + app.Post("/quiz/template", s.TemplateCopy) + + // question manipulating handlers + app.Post("/question/create", s.CreateQuestion) + app.Post("/question/getList", s.GetQuestionList) + app.Patch("/question/edit", s.UpdateQuestion) + app.Post("/question/copy", s.CopyQuestion) + app.Post("/question/history", s.GetQuestionHistory) + app.Delete("/question/delete", s.DeleteQuestion) + + // account handlers + app.Get("/account/get", s.getCurrentAccount) + app.Post("/account/create", s.createAccount) + app.Delete("/account/delete", s.deleteAccount) + app.Get("/accounts", s.getAccounts) + app.Get("/privilege/:userId", s.getPrivilegeByUserID) + app.Delete("/account/:userId", s.deleteAccountByUserID) + app.Post("/account/manualdone", s.ManualDone) + app.Post("/account/leadtarget", s.PostLeadTarget) + app.Delete("/account/leadtarget/:id", s.DeleteLeadTarget) + app.Get("/account/leadtarget/:quizID", s.GetLeadTarget) + app.Put("/account/leadtarget", s.UpdateLeadTarget) + + // result handlers + app.Post("/results/getResults/:quizId", s.GetResultsByQuizID) + app.Delete("/results/delete/:resultId", s.DelResultByID) + app.Patch("/result/seen", s.SetStatus) + app.Post("/results/:quizID/export", s.ExportResultsToCSV) + app.Get("/result/:resultID", s.GetResultAnswers) + + // statistics handlers + app.Post("/statistic/:quizID/devices", s.GetDeviceStatistics) + app.Post("/statistic/:quizID/general", s.GetGeneralStatistics) + app.Post("/statistic/:quizID/questions", s.GetQuestionsStatistics) + app.Post("/statistic", s.AllServiceStatistics) + app.Get("/statistics/:quizID/pipelines", s.GetPipelinesStatistics) + + //telegram handlers + app.Get("/telegram/pool", s.GetPoolTgAccounts) + app.Post("/telegram/create", s.AddingTgAccount) + app.Delete("/telegram/:id", s.DeleteTgAccountByID) + app.Post("/telegram/setCode", s.SettingTgCode) +}