Compare commits
53 Commits
Author | SHA1 | Date | |
---|---|---|---|
cf3671ea13 | |||
c22d9ecdd7 | |||
5e260c79bd | |||
70f9a40a75 | |||
495ef84e61 | |||
d539b6bda4 | |||
c905095d4c | |||
a1b06a268f | |||
1a70f30c4f | |||
806f202f0c | |||
63c0845d7a | |||
f4b739015e | |||
b0fdafa3b4 | |||
9f99282f71 | |||
b6d4ab132f | |||
8d6eda4bc9 | |||
5912cb1528 | |||
970963278d | |||
91922628ad | |||
4000e61b90 | |||
0bda592bbd | |||
ed8959d942 | |||
ec0e0cde43 | |||
7e8e882bfc | |||
9a7b3f26ea | |||
f45aa08206 | |||
530aed07e8 | |||
27f1265317 | |||
578be84bb7 | |||
2d76b4bafd | |||
4c7c02bf15 | |||
64d8340035 | |||
9db181dfdf | |||
772df884b7 | |||
103c6caaf2 | |||
00fa7f10af | |||
c824f10dc0 | |||
e038b66109 | |||
aee8cdd1ee | |||
7c6521b8b3 | |||
65e334c6b6 | |||
a08a2f2ac0 | |||
25a43006d3 | |||
79dadbd93b | |||
70294255c8 | |||
ddd462347d | |||
c8257b1ea4 | |||
42413a877f | |||
5b06e730ab | |||
345228a19b | |||
0ccee75e14 | |||
c9dec5db34 | |||
7cb9694fb3 |
163
.gitignore
vendored
Normal file
163
.gitignore
vendored
Normal file
@ -0,0 +1,163 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,goland,go
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,goland,go
|
||||
|
||||
### Go ###
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
### GoLand ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
.idea
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### GoLand Patch ###
|
||||
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||
|
||||
# *.iml
|
||||
# modules.xml
|
||||
# .idea/misc.xml
|
||||
# *.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
||||
.idea/**/sonarlint/
|
||||
|
||||
# SonarQube Plugin
|
||||
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
||||
.idea/**/sonarIssues.xml
|
||||
|
||||
# Markdown Navigator plugin
|
||||
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
||||
.idea/**/markdown-navigator.xml
|
||||
.idea/**/markdown-navigator-enh.xml
|
||||
.idea/**/markdown-navigator/
|
||||
|
||||
# Cache file creation bug
|
||||
# See https://youtrack.jetbrains.com/issue/JBR-2257
|
||||
.idea/$CACHE_FILE$
|
||||
|
||||
# CodeStream plugin
|
||||
# https://plugins.jetbrains.com/plugin/12206-codestream
|
||||
.idea/codestream.xml
|
||||
|
||||
# Azure Toolkit for IntelliJ plugin
|
||||
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
||||
.idea/**/azureSettings.xml
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,goland,go
|
||||
main
|
||||
.golangci.yml
|
35
cmd/main.go
Normal file
35
cmd/main.go
Normal file
@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/app"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/initialize"
|
||||
"syscall"
|
||||
|
||||
"go.uber.org/zap"
|
||||
// import for automatically updating linter rules
|
||||
_ "penahub.gitlab.yandexcloud.net/devops/linters/golang.git/pkg/dummy"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logger, err := zap.NewProduction()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to initialize logger: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
config, err := initialize.LoadConfig()
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to load config", zap.Error(err))
|
||||
}
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
if err = app.Run(ctx, *config, logger); err != nil {
|
||||
logger.Fatal("App exited with error", zap.Error(err))
|
||||
}
|
||||
}
|
86
cmd/validator/main.go
Normal file
86
cmd/validator/main.go
Normal file
@ -0,0 +1,86 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"gitea.pena/PenaSide/common/validate"
|
||||
"github.com/caarlos0/env/v8"
|
||||
"log"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/initialize"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg, err := loadConfig()
|
||||
if err != nil {
|
||||
log.Fatalf("error loading config: %v", err)
|
||||
}
|
||||
|
||||
err = validateNotEmpty(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("error validating config: %v", err)
|
||||
}
|
||||
|
||||
err = validate.ValidateKafka([]string{cfg.KafkaBrokers}, cfg.KafkaTopicQueueBitrix)
|
||||
if err != nil {
|
||||
log.Fatalf("error validating Kafka: %v", err)
|
||||
}
|
||||
|
||||
err = validate.ValidateRedis(cfg.RedisHost, cfg.RedisPassword, cfg.RedisDB)
|
||||
if err != nil {
|
||||
log.Fatalf("error validating Redis: %v", err)
|
||||
}
|
||||
|
||||
err = validate.ValidateEncryptKeys(&cfg.ExternalCfg.EncryptCommon)
|
||||
if err != nil {
|
||||
log.Fatalf("error validating EncryptKeys: %v", err)
|
||||
}
|
||||
|
||||
_, err = dal.NewBitrixDal(context.TODO(), cfg.PostgresURL)
|
||||
if err != nil {
|
||||
log.Fatalf("error connecting to postgres: %v", err)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func loadConfig() (initialize.Config, error) {
|
||||
var cfg initialize.Config
|
||||
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func validateNotEmpty(cfg initialize.Config) error {
|
||||
if cfg.ClientHttpURL == "" {
|
||||
return errors.New("client http url cannot be empty")
|
||||
}
|
||||
|
||||
if cfg.BitrixIntegrationID == "" {
|
||||
return errors.New("bitrix integration id cannot be empty")
|
||||
}
|
||||
|
||||
if cfg.QuizIntegrationsRedirectURL == "" {
|
||||
return errors.New("quiz integrations redirect url cannot be empty")
|
||||
}
|
||||
|
||||
if cfg.OauthReturnURL == "" {
|
||||
return errors.New("oauth returning url cannot be empty")
|
||||
}
|
||||
|
||||
if cfg.BitrixIntegrationSecret == "" {
|
||||
return errors.New("bitrix integration secret cannot be empty")
|
||||
}
|
||||
|
||||
if cfg.ExternalCfg.EncryptCommon.PrivKey == "" {
|
||||
return errors.New("no private key provided")
|
||||
}
|
||||
|
||||
if cfg.ExternalCfg.EncryptCommon.PubKey == "" {
|
||||
return errors.New("no public key provided")
|
||||
}
|
||||
return nil
|
||||
}
|
51
deployments/local/docker-compose.yaml
Normal file
51
deployments/local/docker-compose.yaml
Normal file
@ -0,0 +1,51 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
zookeeper:
|
||||
image: wurstmeister/zookeeper
|
||||
ports:
|
||||
- "2181:2181"
|
||||
|
||||
kafka:
|
||||
image: wurstmeister/kafka
|
||||
ports:
|
||||
- "9092:9092"
|
||||
environment:
|
||||
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
|
||||
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT
|
||||
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
|
||||
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
||||
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
||||
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
|
||||
KAFKA_CREATE_TOPICS: "test-topic:1:1"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
bitrix-postgres:
|
||||
image: postgres
|
||||
environment:
|
||||
POSTGRES_PASSWORD: Redalert2
|
||||
POSTGRES_USER: squiz
|
||||
POSTGRES_DB: squiz
|
||||
volumes:
|
||||
- bitrix-postgres:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 35432:5432
|
||||
healthcheck:
|
||||
test: pg_isready -U squiz
|
||||
interval: 2s
|
||||
timeout: 2s
|
||||
retries: 10
|
||||
|
||||
bitrix-redis:
|
||||
image: redis:latest
|
||||
restart: always
|
||||
ports:
|
||||
- "6379:6379"
|
||||
command: [ "redis-server", "--appendonly", "yes", "--requirepass", "admin" ]
|
||||
|
||||
volumes:
|
||||
bitrix-postgres:
|
||||
|
||||
|
||||
|
0
deployments/staging/docker-compose.yaml
Normal file
0
deployments/staging/docker-compose.yaml
Normal file
0
deployments/test/docker-compose.yaml
Normal file
0
deployments/test/docker-compose.yaml
Normal file
64
go.mod
Normal file
64
go.mod
Normal file
@ -0,0 +1,64 @@
|
||||
module penahub.gitlab.yandexcloud.net/backend/quiz/bitrix
|
||||
|
||||
go 1.23.2
|
||||
|
||||
toolchain go1.23.4
|
||||
|
||||
require (
|
||||
gitea.pena/PenaSide/common v0.0.0-20250103085335-91ea31fee517
|
||||
github.com/caarlos0/env/v8 v8.0.0
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/gofiber/fiber/v2 v2.52.5
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/rs/xid v1.6.0
|
||||
github.com/twmb/franz-go v1.18.0
|
||||
go.uber.org/zap v1.27.0
|
||||
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20241024141027-0c6f373d187c
|
||||
penahub.gitlab.yandexcloud.net/devops/linters/golang.git v0.0.0-20240829220549-d35409b619a3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/ClickHouse/clickhouse-go v1.5.4 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
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.81 // indirect
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pioz/faker v1.7.3 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/tealeg/xlsx v1.0.5 // indirect
|
||||
github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf // indirect
|
||||
github.com/twmb/franz-go/pkg/kmsg v1.9.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
go.mongodb.org/mongo-driver v1.13.1 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d // indirect
|
||||
)
|
193
go.sum
Normal file
193
go.sum
Normal file
@ -0,0 +1,193 @@
|
||||
gitea.pena/PenaSide/common v0.0.0-20250103085335-91ea31fee517 h1:EgBe8VcdPwmxbSzYLndncP+NmR73uYuXxkTeDlEttEE=
|
||||
gitea.pena/PenaSide/common v0.0.0-20250103085335-91ea31fee517/go.mod h1:91EuBCgcqgJ6mG36n2pds8sPwwfaJytLWOzY3h2YFKU=
|
||||
github.com/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0=
|
||||
github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=
|
||||
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
|
||||
github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0=
|
||||
github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
|
||||
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.81 h1:SzhMN0TQ6T/xSBu6Nvw3M5M8voM+Ht8RH3hE8S7zxaA=
|
||||
github.com/minio/minio-go/v7 v7.0.81/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pioz/faker v1.7.3 h1:Tez8Emuq0UN+/d6mo3a9m/9ZZ/zdfJk0c5RtRatrceM=
|
||||
github.com/pioz/faker v1.7.3/go.mod h1:xSpay5w/oz1a6+ww0M3vfpe40pSIykeUPeWEc3TvVlc=
|
||||
github.com/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 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
|
||||
github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
|
||||
github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf h1:TJJm6KcBssmbWzplF5lzixXl1RBAi/ViPs1GaSOkhwo=
|
||||
github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf/go.mod h1:1FsorU3vnXO9xS9SrhUp8fRb/6H/Zfll0rPt1i4GWaA=
|
||||
github.com/twmb/franz-go v1.18.0 h1:25FjMZfdozBywVX+5xrWC2W+W76i0xykKjTdEeD2ejw=
|
||||
github.com/twmb/franz-go v1.18.0/go.mod h1:zXCGy74M0p5FbXsLeASdyvfLFsBvTubVqctIaa5wQ+I=
|
||||
github.com/twmb/franz-go/pkg/kmsg v1.9.0 h1:JojYUph2TKAau6SBtErXpXGC7E3gg4vGZMv9xFU/B6M=
|
||||
github.com/twmb/franz-go/pkg/kmsg v1.9.0/go.mod h1:CMbfazviCyY6HM0SXuG5t9vOwYDHRCSrJJyBAe5paqg=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
|
||||
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d h1:gbaDt35HMDqOK84WYmDIlXMI7rstUcRqNttaT6Kx1do=
|
||||
penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d/go.mod h1:lTmpjry+8evVkXWbEC+WMOELcFkRD1lFMc7J09mOndM=
|
||||
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20241024141027-0c6f373d187c h1:wViTf9OITkoGW/H5zB9kNIxDeEAPaoPOXZRhgtRTDU4=
|
||||
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20241024141027-0c6f373d187c/go.mod h1:uOuosXduBzd2WbLH6TDZO7ME7ZextulA662oZ6OsoB0=
|
||||
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=
|
161
internal/app/app.go
Normal file
161
internal/app/app.go
Normal file
@ -0,0 +1,161 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/brokers"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/controllers"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/initialize"
|
||||
http "penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/server"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/service"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/workers/data_updater"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/workers/limiter"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/workers/post_deals_worker"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/workers/queueUpdater"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/workers_methods"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/pkg/bitrixClient"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/pkg/closer"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, config initialize.Config, logger *zap.Logger) error {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error("Recovered from a panic", zap.Any("error", r))
|
||||
}
|
||||
}()
|
||||
|
||||
logger.Info("App started", zap.Any("config", config))
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
shutdownGroup := closer.NewCloserGroup()
|
||||
|
||||
kafka, err := initialize.KafkaConsumerInit(ctx, config)
|
||||
if err != nil {
|
||||
logger.Error("error init kafka consumer", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
producer := brokers.NewProducer(brokers.ProducerDeps{
|
||||
KafkaClient: kafka,
|
||||
Logger: logger,
|
||||
})
|
||||
|
||||
bitrixRepo, err := dal.NewBitrixDal(ctx, config.PostgresURL)
|
||||
if err != nil {
|
||||
logger.Error("error init bitrix repo in common repo", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// https://apidocs.bitrix24.ru/limits.html
|
||||
rateLimiter := limiter.NewRateLimiter(ctx, 50, 2*time.Second)
|
||||
|
||||
bitrixClientApi := bitrixClient.NewBitrixClient(bitrixClient.BitrixDeps{
|
||||
Logger: logger,
|
||||
RedirectionURL: config.OauthReturnURL,
|
||||
IntegrationID: config.BitrixIntegrationID,
|
||||
IntegrationSecret: config.BitrixIntegrationSecret,
|
||||
RateLimiter: rateLimiter,
|
||||
})
|
||||
|
||||
svc := service.NewService(service.Deps{
|
||||
Repository: bitrixRepo,
|
||||
Logger: logger,
|
||||
BitrixClient: bitrixClientApi,
|
||||
Producer: producer,
|
||||
Config: config,
|
||||
Encrypt: &config.ExternalCfg.EncryptCommon,
|
||||
})
|
||||
|
||||
cntrlDeps := controllers.Deps{
|
||||
Service: svc,
|
||||
Logger: logger,
|
||||
RedirectURL: config.QuizIntegrationsRedirectURL,
|
||||
Encrypt: &config.ExternalCfg.EncryptCommon,
|
||||
}
|
||||
|
||||
controller := controllers.NewController(cntrlDeps)
|
||||
|
||||
webhookController := controllers.NewWebhookController(cntrlDeps)
|
||||
|
||||
workerMethods := workers_methods.NewWorkersMethods(workers_methods.Deps{
|
||||
Repo: bitrixRepo,
|
||||
BitrixClient: bitrixClientApi,
|
||||
Logger: logger,
|
||||
})
|
||||
|
||||
dataUpdater := data_updater.NewDataUpdaterWC(data_updater.Deps{
|
||||
Logger: logger,
|
||||
Producer: producer,
|
||||
})
|
||||
|
||||
queUpdater := queueUpdater.NewQueueUpdater(queueUpdater.Deps{
|
||||
Logger: logger,
|
||||
KafkaClient: kafka,
|
||||
Methods: workerMethods,
|
||||
})
|
||||
|
||||
dealsPoster := post_deals_worker.NewDealsWC(post_deals_worker.Deps{
|
||||
BitrixRepo: bitrixRepo,
|
||||
BitrixClient: bitrixClientApi,
|
||||
Logger: logger,
|
||||
})
|
||||
//
|
||||
//fieldsPoster := post_fields_worker.NewPostFieldsWC(post_fields_worker.Deps{
|
||||
// AmoRepo: amoRepo,
|
||||
// AmoClient: amoClient,
|
||||
// RedisRepo: redisRepo,
|
||||
// Logger: logger,
|
||||
//})
|
||||
//
|
||||
go dataUpdater.Start(ctx)
|
||||
go queUpdater.Start(ctx)
|
||||
go dealsPoster.Start(ctx)
|
||||
//go fieldsPoster.Start(ctx)
|
||||
|
||||
server := http.NewServer(http.ServerConfig{
|
||||
Controllers: []http.Controller{
|
||||
controller,
|
||||
webhookController,
|
||||
},
|
||||
})
|
||||
|
||||
go func() {
|
||||
if err := server.Start(config.ClientHttpURL); err != nil {
|
||||
logger.Error("Server startup error", zap.Error(err))
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
server.ListRoutes()
|
||||
|
||||
shutdownGroup.Add(closer.CloserFunc(server.Shutdown))
|
||||
shutdownGroup.Add(closer.CloserFunc(bitrixRepo.Close))
|
||||
//shutdownGroup.Add(closer.CloserFunc(redisRepo.Close))
|
||||
shutdownGroup.Add(closer.CloserFunc(rateLimiter.Stop))
|
||||
shutdownGroup.Add(closer.CloserFunc(dataUpdater.Stop))
|
||||
shutdownGroup.Add(closer.CloserFunc(queUpdater.Stop))
|
||||
shutdownGroup.Add(closer.CloserFunc(dealsPoster.Stop))
|
||||
//shutdownGroup.Add(closer.CloserFunc(fieldsPoster.Stop))
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer timeoutCancel()
|
||||
if err := shutdownGroup.Call(timeoutCtx); err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
logger.Error("Shutdown timed out", zap.Error(err))
|
||||
} else {
|
||||
logger.Error("Failed to shutdown services gracefully", zap.Error(err))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("Application has stopped")
|
||||
return nil
|
||||
}
|
45
internal/brokers/producer.go
Normal file
45
internal/brokers/producer.go
Normal file
@ -0,0 +1,45 @@
|
||||
package brokers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/twmb/franz-go/pkg/kgo"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
|
||||
)
|
||||
|
||||
type ProducerDeps struct {
|
||||
KafkaClient *kgo.Client
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
type Producer struct {
|
||||
kafkaClient *kgo.Client
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewProducer(deps ProducerDeps) *Producer {
|
||||
return &Producer{
|
||||
logger: deps.Logger,
|
||||
kafkaClient: deps.KafkaClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Producer) ToKafkaUpdate(ctx context.Context, message models.KafkaMessage) error {
|
||||
bytes, err := json.Marshal(message)
|
||||
if err != nil {
|
||||
p.logger.Error("error marshal message to kafka", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
responses := p.kafkaClient.ProduceSync(ctx, &kgo.Record{Value: bytes})
|
||||
for _, response := range responses {
|
||||
if response.Err != nil {
|
||||
p.logger.Error("failed to send message on update kafka", zap.Error(response.Err))
|
||||
return response.Err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
47
internal/controllers/fields.go
Normal file
47
internal/controllers/fields.go
Normal file
@ -0,0 +1,47 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
|
||||
)
|
||||
|
||||
func (c *Controller) GetFieldsWithPagination(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
req, err := extractParams(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := c.service.GetFieldsWithPagination(ctx.Context(), req, accountID)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, pj_errors.ErrNotFound):
|
||||
return ctx.Status(fiber.StatusNotFound).SendString("fields for this user not found")
|
||||
default:
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
}
|
||||
return ctx.Status(fiber.StatusOK).JSON(response)
|
||||
}
|
||||
|
||||
func (c *Controller) UpdateListCustom(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
//accountID := "64f2cd7a7047f28fdabf6d9e"
|
||||
|
||||
err := c.service.UpdateListCustom(ctx.Context(), accountID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
|
||||
return ctx.SendStatus(fiber.StatusOK)
|
||||
}
|
81
internal/controllers/initial.go
Normal file
81
internal/controllers/initial.go
Normal file
@ -0,0 +1,81 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"gitea.pena/PenaSide/common/encrypt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/service"
|
||||
)
|
||||
|
||||
type Deps struct {
|
||||
Service *service.Service
|
||||
Logger *zap.Logger
|
||||
Encrypt *encrypt.Encrypt
|
||||
RedirectURL string
|
||||
}
|
||||
|
||||
type Controller struct {
|
||||
service *service.Service
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewController(deps Deps) *Controller {
|
||||
return &Controller{
|
||||
service: deps.Service,
|
||||
logger: deps.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) Register(router fiber.Router) {
|
||||
router.Patch("/users", c.UpdateListUsers)
|
||||
router.Get("/users", c.GettingUserWithPagination)
|
||||
router.Delete("/account", c.SoftDeleteAccount)
|
||||
router.Get("/account", c.GetCurrentAccount)
|
||||
router.Post("/account", c.ConnectAccount)
|
||||
router.Get("/steps", c.GetStepsWithPagination)
|
||||
router.Patch("/steps", c.UpdateListSteps)
|
||||
router.Patch("/pipelines", c.UpdateListPipelines)
|
||||
router.Get("/pipelines", c.GetPipelinesWithPagination)
|
||||
router.Patch("/rules/:quizID", c.ChangeQuizSettings)
|
||||
router.Post("/rules/:quizID", c.SetQuizSettings)
|
||||
router.Get("/rules/:quizID", c.GettingQuizRules)
|
||||
//router.Get("/tags", c.GetTagsWithPagination)
|
||||
//router.Patch("/tags", c.UpdateListTags)
|
||||
router.Get("/fields", c.GetFieldsWithPagination)
|
||||
router.Patch("/fields", c.UpdateListCustom)
|
||||
router.Get("/", c.Bitrix)
|
||||
}
|
||||
|
||||
func (c *Controller) Name() string {
|
||||
return "bitrix"
|
||||
}
|
||||
|
||||
type WebhookController struct {
|
||||
service *service.Service
|
||||
logger *zap.Logger
|
||||
encrypt *encrypt.Encrypt
|
||||
redirectURL string
|
||||
}
|
||||
|
||||
func NewWebhookController(deps Deps) *WebhookController {
|
||||
return &WebhookController{
|
||||
service: deps.Service,
|
||||
logger: deps.Logger,
|
||||
encrypt: deps.Encrypt,
|
||||
redirectURL: deps.RedirectURL,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WebhookController) Register(router fiber.Router) {
|
||||
router.Get("/create", c.WebhookCreate)
|
||||
//router.Delete("/delete", c.WebhookDelete)
|
||||
}
|
||||
|
||||
func (c *WebhookController) Name() string {
|
||||
return "webhook"
|
||||
}
|
||||
|
||||
// todo check для чего
|
||||
func (c *Controller) Bitrix(ctx *fiber.Ctx) error {
|
||||
return ctx.Status(fiber.StatusOK).JSON("OK")
|
||||
}
|
37
internal/controllers/middleware.go
Normal file
37
internal/controllers/middleware.go
Normal file
@ -0,0 +1,37 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func extractParams(ctx *fiber.Ctx) (*model.PaginationReq, error) {
|
||||
pageStr := ctx.Query("page")
|
||||
sizeStr := ctx.Query("size")
|
||||
page := 1
|
||||
size := 25
|
||||
|
||||
if pageStr != "" {
|
||||
pageNew, err := strconv.Atoi(pageStr)
|
||||
if err != nil {
|
||||
return nil, ctx.Status(fiber.StatusBadRequest).SendString("Invalid page parameter")
|
||||
}
|
||||
page = pageNew
|
||||
}
|
||||
|
||||
if sizeStr != "" {
|
||||
sizeNew, err := strconv.Atoi(sizeStr)
|
||||
if err != nil {
|
||||
return nil, ctx.Status(fiber.StatusBadRequest).SendString("Invalid size parameter")
|
||||
}
|
||||
size = sizeNew
|
||||
}
|
||||
|
||||
req := model.PaginationReq{
|
||||
Page: page,
|
||||
Size: int32(size),
|
||||
}
|
||||
|
||||
return &req, nil
|
||||
}
|
47
internal/controllers/pipelines.go
Normal file
47
internal/controllers/pipelines.go
Normal file
@ -0,0 +1,47 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
|
||||
)
|
||||
|
||||
func (c *Controller) UpdateListPipelines(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
//accountID := "64f2cd7a7047f28fdabf6d9e"
|
||||
|
||||
err := c.service.UpdateListPipelines(ctx.Context(), accountID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
|
||||
return ctx.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func (c *Controller) GetPipelinesWithPagination(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
req, err := extractParams(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := c.service.GetPipelinesWithPagination(ctx.Context(), req, accountID)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, pj_errors.ErrNotFound):
|
||||
return ctx.Status(fiber.StatusNotFound).SendString("pipelines for this user not found")
|
||||
default:
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
}
|
||||
return ctx.Status(fiber.StatusOK).JSON(response)
|
||||
}
|
110
internal/controllers/rules.go
Normal file
110
internal/controllers/rules.go
Normal file
@ -0,0 +1,110 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/lib/pq"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (c *Controller) ChangeQuizSettings(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
quizID := ctx.Params("quizID")
|
||||
if quizID == "" {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("quizID is nil")
|
||||
}
|
||||
|
||||
quizIDInt, err := strconv.Atoi(quizID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("failed convert quizID to int")
|
||||
}
|
||||
|
||||
//accountID := "64f2cd7a7047f28fdabf6d9e"
|
||||
|
||||
var request model.BitrixRulesReq
|
||||
if err := ctx.BodyParser(&request); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||
}
|
||||
|
||||
err = c.service.ChangeQuizSettings(ctx.Context(), &request, accountID, quizIDInt)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, pj_errors.ErrNotFound):
|
||||
return ctx.Status(fiber.StatusNotFound).SendString("rule not found")
|
||||
default:
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
}
|
||||
return ctx.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func (c *Controller) SetQuizSettings(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
quizID := ctx.Params("quizID")
|
||||
if quizID == "" {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("quizID is nil")
|
||||
}
|
||||
|
||||
quizIDInt, err := strconv.Atoi(quizID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("failed convert quizID to int")
|
||||
}
|
||||
|
||||
//accountID := "64f2cd7a7047f28fdabf6d9e"
|
||||
|
||||
var request model.BitrixRulesReq
|
||||
if err := ctx.BodyParser(&request); err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request payload"})
|
||||
}
|
||||
|
||||
err = c.service.SetQuizSettings(ctx.Context(), &request, accountID, quizIDInt)
|
||||
if err != nil {
|
||||
pqErr, ok := err.(*pq.Error)
|
||||
if ok && pqErr.Code == "23505" {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("quiz settings already exist for accountID %s and quizID %d", accountID, quizIDInt))
|
||||
}
|
||||
switch {
|
||||
case errors.Is(err, pj_errors.ErrNotFound):
|
||||
return ctx.Status(fiber.StatusNotFound).SendString("not found user for this rule")
|
||||
default:
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
}
|
||||
return ctx.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func (c *Controller) GettingQuizRules(ctx *fiber.Ctx) error {
|
||||
quizID := ctx.Params("quizID")
|
||||
if quizID == "" {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("quizID is nil")
|
||||
}
|
||||
|
||||
quizIDInt, err := strconv.Atoi(quizID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("failed convert quizID to int")
|
||||
}
|
||||
|
||||
response, err := c.service.GettingQuizRules(ctx.Context(), quizIDInt)
|
||||
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, pj_errors.ErrNotFound):
|
||||
return ctx.Status(fiber.StatusNotFound).SendString("rule not found")
|
||||
default:
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
}
|
||||
return ctx.Status(fiber.StatusOK).JSON(response)
|
||||
}
|
47
internal/controllers/steps.go
Normal file
47
internal/controllers/steps.go
Normal file
@ -0,0 +1,47 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
|
||||
)
|
||||
|
||||
func (c *Controller) GetStepsWithPagination(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
req, err := extractParams(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := c.service.GetStepsWithPagination(ctx.Context(), req, accountID)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, pj_errors.ErrNotFound):
|
||||
return ctx.Status(fiber.StatusNotFound).SendString("steps for this user not found")
|
||||
default:
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
}
|
||||
return ctx.Status(fiber.StatusOK).JSON(response)
|
||||
}
|
||||
|
||||
func (c *Controller) UpdateListSteps(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
//accountID := "64f2cd7a7047f28fdabf6d9e"
|
||||
|
||||
err := c.service.UpdateListSteps(ctx.Context(), accountID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
|
||||
return ctx.SendStatus(fiber.StatusOK)
|
||||
}
|
40
internal/controllers/tags.go
Normal file
40
internal/controllers/tags.go
Normal file
@ -0,0 +1,40 @@
|
||||
package controllers
|
||||
|
||||
//func (c *Controller) GetTagsWithPagination(ctx *fiber.Ctx) error {
|
||||
// accountID, ok := middleware.GetAccountId(ctx)
|
||||
// if !ok {
|
||||
// return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
// }
|
||||
//
|
||||
// req, err := extractParams(ctx)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// response, err := c.service.GetTagsWithPagination(ctx.Context(), req, accountID)
|
||||
// if err != nil {
|
||||
// switch {
|
||||
// case errors.Is(err, pj_errors.ErrNotFound):
|
||||
// return ctx.Status(fiber.StatusNotFound).SendString("tags for this user not found")
|
||||
// default:
|
||||
// return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
// }
|
||||
// }
|
||||
// return ctx.Status(fiber.StatusOK).JSON(response)
|
||||
//}
|
||||
//
|
||||
//func (c *Controller) UpdateListTags(ctx *fiber.Ctx) error {
|
||||
// accountID, ok := middleware.GetAccountId(ctx)
|
||||
// if !ok {
|
||||
// return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
// }
|
||||
//
|
||||
// //accountID := "654a8909725f47e926f0bebc"
|
||||
//
|
||||
// err := c.service.UpdateListTags(ctx.Context(), accountID)
|
||||
// if err != nil {
|
||||
// return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
// }
|
||||
//
|
||||
// return ctx.SendStatus(fiber.StatusOK)
|
||||
//}
|
92
internal/controllers/user.go
Normal file
92
internal/controllers/user.go
Normal file
@ -0,0 +1,92 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
|
||||
)
|
||||
|
||||
func (c *Controller) UpdateListUsers(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
//accountID := "654a8909725f47e926f0bebc"
|
||||
|
||||
err := c.service.UpdateListUsers(ctx.Context(), accountID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
|
||||
return ctx.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func (c *Controller) GettingUserWithPagination(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
req, err := extractParams(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := c.service.GettingUserWithPagination(ctx.Context(), req, accountID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
return ctx.Status(fiber.StatusOK).JSON(response)
|
||||
}
|
||||
|
||||
func (c *Controller) SoftDeleteAccount(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
err := c.service.SoftDeleteAccount(ctx.Context(), accountID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
|
||||
}
|
||||
return ctx.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
|
||||
func (c *Controller) GetCurrentAccount(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
response, err := c.service.GetCurrentAccount(ctx.Context(), accountID)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, pj_errors.ErrNotFound):
|
||||
return ctx.Status(fiber.StatusNotFound).SendString("user not found")
|
||||
default:
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
}
|
||||
return ctx.Status(fiber.StatusOK).JSON(response)
|
||||
}
|
||||
|
||||
func (c *Controller) ConnectAccount(ctx *fiber.Ctx) error {
|
||||
accountID, ok := middleware.GetAccountId(ctx)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
|
||||
}
|
||||
|
||||
//accountID := "64f2cd7a7047f28fdabf6d9e"
|
||||
|
||||
response, err := c.service.ConnectAccount(ctx.Context(), accountID)
|
||||
if err != nil {
|
||||
c.logger.Error("error connect account", zap.Error(err))
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
|
||||
return ctx.Status(fiber.StatusOK).JSON(response)
|
||||
}
|
90
internal/controllers/webhook.go
Normal file
90
internal/controllers/webhook.go
Normal file
@ -0,0 +1,90 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/service"
|
||||
)
|
||||
|
||||
// https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=99&LESSON_ID=2486
|
||||
func (c *WebhookController) WebhookCreate(ctx *fiber.Ctx) error {
|
||||
code := ctx.Query("code") // первый авторизационный код
|
||||
domain := ctx.Query("domain") // домен портала, на котором происходит авторизация
|
||||
state := ctx.Query("state") // значение, переданное в первом запросе
|
||||
scope := ctx.Query("scope") // список прав доступа к REST API
|
||||
memberID := ctx.Query("member_id") // уникальный идентификатор портала - id битрикса главного
|
||||
serverDomain := ctx.Query("server_domain") // домен сервера авторизации
|
||||
|
||||
if code == "" || domain == "" || memberID == "" || serverDomain == "" {
|
||||
c.logger.Error("Missing required fields", zap.String("code", code), zap.String("domain", domain), zap.String("member_id", memberID), zap.String("server_domain", serverDomain))
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Missing required fields")
|
||||
}
|
||||
|
||||
if state == "" {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("State cannot be empty")
|
||||
}
|
||||
|
||||
accountID, err := c.encrypt.DecryptStr([]byte(state))
|
||||
if err != nil {
|
||||
c.logger.Error("Error deserializing Protobuf message", zap.Error(err))
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("Failed to process state parameter")
|
||||
}
|
||||
|
||||
if accountID == "" {
|
||||
c.logger.Error("AccountID is missing from state")
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid state parameter")
|
||||
}
|
||||
|
||||
req := service.ParamsWebhookCreate{
|
||||
Code: code,
|
||||
Domain: domain,
|
||||
AccountID: accountID,
|
||||
MemberID: memberID,
|
||||
Scope: scope,
|
||||
ServerDomain: serverDomain,
|
||||
}
|
||||
|
||||
err = c.service.WebhookCreate(ctx.Context(), req)
|
||||
if err != nil {
|
||||
c.logger.Error("Error creating webhook", zap.Error(err))
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("Internal Server Error: %v", err.Error()))
|
||||
}
|
||||
|
||||
return ctx.Redirect(c.redirectURL)
|
||||
}
|
||||
|
||||
//// todo проверить надо есть ли такое вообще
|
||||
//func (c *WebhookController) WebhookDelete(ctx *fiber.Ctx) error {
|
||||
// clientUUID := ctx.Query("client_uuid")
|
||||
// signature := ctx.Query("signature")
|
||||
// amoIDStr := ctx.Query("account_id")
|
||||
//
|
||||
// fmt.Println(clientUUID)
|
||||
// fmt.Println(signature)
|
||||
// fmt.Println(amoIDStr)
|
||||
//
|
||||
// if clientUUID == "" || signature == "" || amoIDStr == "" {
|
||||
// return ctx.Status(fiber.StatusBadRequest).SendString("some nil values")
|
||||
// }
|
||||
//
|
||||
// amoID, err := strconv.Atoi(amoIDStr)
|
||||
// if err != nil {
|
||||
// return ctx.Status(fiber.StatusBadRequest).SendString("invalid account_id type")
|
||||
// }
|
||||
//
|
||||
// if !c.verify.CheckIntegrationID(clientUUID) {
|
||||
// return ctx.Status(fiber.StatusUnauthorized).SendString("invalid hook signature")
|
||||
// }
|
||||
//
|
||||
// if !c.verify.VerifySignature(clientUUID, signature, amoID) {
|
||||
// return ctx.Status(fiber.StatusUnauthorized).SendString("invalid hook signature")
|
||||
// }
|
||||
//
|
||||
// err = c.service.WebhookDelete(ctx.Context(), amoID)
|
||||
// if err != nil {
|
||||
// return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
// }
|
||||
//
|
||||
// return ctx.SendStatus(fiber.StatusOK)
|
||||
//}
|
42
internal/initialize/config.go
Normal file
42
internal/initialize/config.go
Normal file
@ -0,0 +1,42 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"gitea.pena/PenaSide/common/encrypt"
|
||||
"github.com/caarlos0/env/v8"
|
||||
"github.com/joho/godotenv"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ClientHttpURL string `env:"CLIENT_HTTP_URL" envDefault:"10.8.0.18:1492"`
|
||||
PostgresURL string `env:"POSTGRES_URL" envDefault:"host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"`
|
||||
KafkaBrokers string `env:"KAFKA_BROKERS" envDefault:"localhost:9092"`
|
||||
KafkaTopicQueueBitrix string `env:"KAFKA_TOPIC_QUEUE_BITRIX" envDefault:"test-topic"`
|
||||
KafkaGroup string `env:"KAFKA_GROUP" envDefault:"bitrixCRM"`
|
||||
RedisHost string `env:"REDIS_HOST" envDefault:"localhost:6379"`
|
||||
RedisPassword string `env:"REDIS_PASSWORD" envDefault:"admin"`
|
||||
RedisDB int `env:"REDIS_DB" envDefault:"2"`
|
||||
BitrixIntegrationID string `env:"BITRIX_INTEGRATION_ID" envDefault:"app.670bd825e44c52.61826940"` // код интеграции
|
||||
QuizIntegrationsRedirectURL string `env:"QUIZ_INTEGRATIONS_REDIRECT_URL" envDefault:"https://squiz.pena.digital/integrations"`
|
||||
// секрет интеграции
|
||||
BitrixIntegrationSecret string `env:"BITRIX_INTEGRATION_SECRET" envDefault:"Ki0MElZXS6dE6tRsGxixri2jmxbxF2Xa4qQpBPziGdAvvLAHJx"`
|
||||
// урл на который будет возвращен пользователь после авторизации это webhook/create get
|
||||
OauthReturnURL string `env:"OAUTH_RETURN_URL" envDefault:"https://squiz.pena.digital/squiz/amocrm/oauth"`
|
||||
ExternalCfg ExternalCfg
|
||||
}
|
||||
|
||||
type ExternalCfg struct {
|
||||
// публичный и приватные ключи для енкрипта и декрипта стейта который передаем в битрикс а потом он приходит
|
||||
EncryptCommon encrypt.Encrypt
|
||||
}
|
||||
|
||||
func LoadConfig() (*Config, error) {
|
||||
if err := godotenv.Load(); err != nil {
|
||||
log.Print("No .env file found")
|
||||
}
|
||||
var config Config
|
||||
if err := env.Parse(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
27
internal/initialize/kafka.go
Normal file
27
internal/initialize/kafka.go
Normal file
@ -0,0 +1,27 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/twmb/franz-go/pkg/kgo"
|
||||
"time"
|
||||
)
|
||||
|
||||
func KafkaConsumerInit(ctx context.Context, config Config) (*kgo.Client, error) {
|
||||
kafkaClient, err := kgo.NewClient(
|
||||
kgo.SeedBrokers(config.KafkaBrokers),
|
||||
kgo.ConsumerGroup(config.KafkaGroup),
|
||||
kgo.DefaultProduceTopic(config.KafkaTopicQueueBitrix),
|
||||
kgo.ConsumeTopics(config.KafkaTopicQueueBitrix),
|
||||
kgo.ConsumeResetOffset(kgo.NewOffset().AfterMilli(time.Now().UnixMilli())),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = kafkaClient.Ping(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kafkaClient, nil
|
||||
}
|
21
internal/initialize/redis.go
Normal file
21
internal/initialize/redis.go
Normal file
@ -0,0 +1,21 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
func Redis(ctx context.Context, cfg Config) (*redis.Client, error) {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: cfg.RedisHost,
|
||||
Password: cfg.RedisPassword,
|
||||
DB: cfg.RedisDB,
|
||||
})
|
||||
|
||||
status := rdb.Ping(ctx)
|
||||
if err := status.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rdb, nil
|
||||
}
|
18
internal/models/createCompany.go
Normal file
18
internal/models/createCompany.go
Normal file
@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
type CompanyReq struct {
|
||||
Fields CompanyFields `json:"fields"`
|
||||
}
|
||||
|
||||
type CompanyFields struct {
|
||||
Title string `json:"TITLE"`
|
||||
AssignedByID int `json:"ASSIGNED_BY_ID,"`
|
||||
Opened string `json:"OPENED"`
|
||||
LeadID int32 `json:"LEAD_ID"`
|
||||
UtmSource string `json:"UTM_SOURCE"`
|
||||
UtmMedium string `json:"UTM_MEDIUM"`
|
||||
UtmCampaign string `json:"UTM_CAMPAIGN"`
|
||||
UtmContent string `json:"UTM_CONTENT"`
|
||||
UtmTerm string `json:"UTM_TERM"`
|
||||
ContactID int32 `json:"CONTACT_ID"`
|
||||
}
|
21
internal/models/createContact.go
Normal file
21
internal/models/createContact.go
Normal file
@ -0,0 +1,21 @@
|
||||
package models
|
||||
|
||||
type CreateContactReq struct {
|
||||
Fields ContactFields `json:"fields"`
|
||||
}
|
||||
|
||||
type ContactFields struct {
|
||||
Name string `json:"NAME"`
|
||||
SecondName string `json:"SECOND_NAME"`
|
||||
LastName string `json:"LAST_NAME"`
|
||||
TypeID string `json:"TYPE_ID"`
|
||||
SourceID string `json:"SOURCE_ID"`
|
||||
Opened string `json:"OPENED"`
|
||||
CompanyIDs []int32 `json:"COMPANY_IDS"`
|
||||
LeadID int32 `json:"LEAD_ID"`
|
||||
UtmSource string `json:"UTM_SOURCE"`
|
||||
UtmMedium string `json:"UTM_MEDIUM"`
|
||||
UtmCampaign string `json:"UTM_CAMPAIGN"`
|
||||
UtmContent string `json:"UTM_CONTENT"`
|
||||
UtmTerm string `json:"UTM_TERM"`
|
||||
}
|
82
internal/models/createDeal.go
Normal file
82
internal/models/createDeal.go
Normal file
@ -0,0 +1,82 @@
|
||||
package models
|
||||
|
||||
type CreatingDealReq struct {
|
||||
Fields CreateDealFields `json:"fields"`
|
||||
//Params struct {
|
||||
// RegisterSonetEvent string `json:"REGISTER_SONET_EVENT"` // Зарегистрировать ли событие добавления сделки в живой ленте. Возможные значения: Y/N def - Y
|
||||
//} `json:"params"`
|
||||
}
|
||||
|
||||
type CreateDealFields struct {
|
||||
Title string `json:"TITLE"` // название
|
||||
TypeID string `json:"TYPE_ID"` // шаг сделки только с "ENTITY_ID":"DEAL_TYPE","STATUS_ID":"SALE"
|
||||
StageID string `json:"STAGE_ID"` // стадия сделки, шаг "ENTITY_ID":"DEAL_STAGE","STATUS_ID":"NEW"
|
||||
CompanyID int32 `json:"COMPANY_ID"` // id компании
|
||||
ContactIDs []int32 `json:"CONTACT_IDS"` // id контакта
|
||||
Opened string `json:"OPENED"` // открыта или нет Y/N
|
||||
AssignedByID int `json:"ASSIGNED_BY_ID"` // ответсвенный
|
||||
CategoryID int32 `json:"CATEGORY_ID"` // воронка id воронки
|
||||
SourceID string `json:"SOURCE_ID"` // тип источника, шаг "ENTITY_ID":"SOURCE","STATUS_ID":"CALL"
|
||||
UtmSource string `json:"UTM_SOURCE"` // Рекламная система (Google-Adwords и другие)
|
||||
UtmMedium string `json:"UTM_MEDIUM"` // Тип трафика. Возможные значения:CPC — объявления, CPM — баннеры
|
||||
UtmCampaign string `json:"UTM_CAMPAIGN"` // Обозначение рекламной кампании
|
||||
UtmContent string `json:"UTM_CONTENT"` // Содержание кампании. Например, для контекстных объявлений
|
||||
UtmTerm string `json:"UTM_TERM"` // Условие поиска кампании. Например, ключевые слова контекстной рекламы
|
||||
|
||||
// todo умаю пока не нужны
|
||||
//IsRecurring string `json:"IS_RECURRING"` // Является ли сделка регулярной (Y/N)
|
||||
//IsReturnCustomer string `json:"IS_RETURN_CUSTOMER"` // Является ли сделка повторной (Y/N)
|
||||
//IsRepeatedApproach string `json:"IS_REPEATED_APPROACH"` // Является ли сделка повторным обращением (Y/N)
|
||||
//Probability int `json:"PROBABILITY"` // Вероятность успеха сделки, %
|
||||
//CurrencyID string `json:"CURRENCY_ID"` // Идентификатор валюты сделки
|
||||
//Opportunity float64 `json:"OPPORTUNITY"` // Сумма сделки
|
||||
//IsManualOpportunity string `json:"IS_MANUAL_OPPORTUNITY"` // Включен ли ручной расчет суммы (Y/N)
|
||||
//TaxValue float64 `json:"TAX_VALUE"` // Сумма налога
|
||||
//BeginDate string `json:"BEGINDATE"` // Дата начала сделки
|
||||
//CloseDate string `json:"CLOSEDATE"` // Дата завершения сделки
|
||||
//Closed string `json:"CLOSED"` // Сделка закрыта (Y/N)
|
||||
//Comments string `json:"COMMENTS"` // Комментарии к сделке
|
||||
//SourceDescription string `json:"SOURCE_DESCRIPTION"` // Описание источника
|
||||
//AdditionalInfo string `json:"ADDITIONAL_INFO"` // Дополнительная информация
|
||||
//LocationID string `json:"LOCATION_ID"` // Местоположение клиента
|
||||
//OriginatorID string `json:"ORIGINATOR_ID"` // Идентификатор источника данных
|
||||
//OriginID string `json:"ORIGIN_ID"` // Идентификатор элемента в источнике данных
|
||||
//Trace string `json:"TRACE"` // Информация для сквозной аналитики
|
||||
//UfCrmCustomField []string `json:"UF_CRM_..."` // Пользовательские поля CRM
|
||||
//ParentID string `json:"PARENT_ID_..."` // Поля связей со смарт-процессами
|
||||
}
|
||||
|
||||
//func (c *CreatingDealReq) FormattingToMap(fieldAnswer map[string]string) map[string]map[string]interface{} {
|
||||
// resultFields := make(map[string]interface{})
|
||||
// result := make(map[string]map[string]interface{})
|
||||
//
|
||||
// fields := reflect.ValueOf(c.Fields)
|
||||
// fieldType := reflect.TypeOf(c.Fields)
|
||||
//
|
||||
// for i := 0; i < fields.NumField(); i++ {
|
||||
// field := fields.Field(i)
|
||||
// fieldName := fieldType.Field(i).Tag.Get("json")
|
||||
//
|
||||
// switch field.Kind() {
|
||||
// case reflect.String:
|
||||
// resultFields[fieldName] = field.String()
|
||||
// case reflect.Int32, reflect.Int:
|
||||
// resultFields[fieldName] = field.Int()
|
||||
// case reflect.Slice:
|
||||
// if field.Type().Elem().Kind() == reflect.Int32 {
|
||||
// resultFields[fieldName] = field.Interface().([]int32)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// for key, value := range fieldAnswer {
|
||||
// resultFields[key] = value
|
||||
// }
|
||||
// result["fields"] = resultFields
|
||||
//
|
||||
// return result
|
||||
//}
|
||||
|
||||
type MultiResp struct {
|
||||
ID int32 `json:"result"`
|
||||
}
|
20
internal/models/createLead.go
Normal file
20
internal/models/createLead.go
Normal file
@ -0,0 +1,20 @@
|
||||
package models
|
||||
|
||||
type CreatingLeadReq struct {
|
||||
Fields CreateLeadFields `json:"fields"`
|
||||
}
|
||||
|
||||
type CreateLeadFields struct {
|
||||
Title string `json:"TITLE"` // название
|
||||
CompanyID int32 `json:"COMPANY_ID"` // id компании
|
||||
ContactIDs []int32 `json:"CONTACT_IDS"` // id контакта
|
||||
Opened string `json:"OPENED"` // открыта или нет Y/N
|
||||
AssignedByID int `json:"ASSIGNED_BY_ID"` // ответсвенный
|
||||
SourceID string `json:"SOURCE_ID"` // тип источника, шаг "ENTITY_ID":"SOURCE","STATUS_ID":"CALL"
|
||||
StatusID string `json:"STATUS_ID"` // Идентификатор стадии лида.
|
||||
UtmSource string `json:"UTM_SOURCE"` // Рекламная система (Google-Adwords и другие)
|
||||
UtmMedium string `json:"UTM_MEDIUM"` // Тип трафика. Возможные значения:CPC — объявления, CPM — баннеры
|
||||
UtmCampaign string `json:"UTM_CAMPAIGN"` // Обозначение рекламной кампании
|
||||
UtmContent string `json:"UTM_CONTENT"` // Содержание кампании. Например, для контекстных объявлений
|
||||
UtmTerm string `json:"UTM_TERM"` // Условие поиска кампании. Например, ключевые слова контекстной рекламы
|
||||
}
|
64
internal/models/createWebHook.go
Normal file
64
internal/models/createWebHook.go
Normal file
@ -0,0 +1,64 @@
|
||||
package models
|
||||
|
||||
type CreateWebHookReq struct {
|
||||
ClientID string `json:"client_id"` // id интеграции
|
||||
ClientSecret string `json:"client_secret"` // Секрет интеграции
|
||||
GrantType string `json:"grant_type"` // Тип авторизационных данных (для кода авторизации – authorization_code)
|
||||
Code string `json:"code"` // Полученный код авторизации
|
||||
}
|
||||
|
||||
type CreateWebHookResp struct {
|
||||
AccessToken string `json:"access_token"` // access token в формате JWT
|
||||
ClientEndpoint string `json:"client_endpoint"`
|
||||
Domain string `json:"domain"`
|
||||
ExpiresIn int64 `json:"expires_in"` // время жизни токена в секундах
|
||||
MemberID string `json:"member_id"` // ид пользователя
|
||||
RefreshToken string `json:"refresh_token"` // токен для обновления access Token
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
type UpdateWebHookReq struct {
|
||||
ClientID string `json:"client_id"` // id интеграции
|
||||
ClientSecret string `json:"client_secret"` // Секрет интеграции
|
||||
GrantType string `json:"grant_type"` // Тип авторизационных данных (для кода авторизации – authorization_code) refresh_token tut
|
||||
RefreshToken string `json:"refresh_token"` // Refresh токен
|
||||
}
|
||||
|
||||
type WebHookRequest interface {
|
||||
SetClientID(str string)
|
||||
SetClientSecret(str string)
|
||||
GetGrantType() string
|
||||
GetToken() string
|
||||
}
|
||||
|
||||
func (req *CreateWebHookReq) SetClientID(str string) {
|
||||
req.ClientID = str
|
||||
}
|
||||
|
||||
func (req *CreateWebHookReq) SetClientSecret(str string) {
|
||||
req.ClientSecret = str
|
||||
}
|
||||
|
||||
func (req *CreateWebHookReq) GetGrantType() string {
|
||||
return req.GrantType
|
||||
}
|
||||
|
||||
func (req *CreateWebHookReq) GetToken() string {
|
||||
return req.Code
|
||||
}
|
||||
|
||||
func (req *UpdateWebHookReq) SetClientID(str string) {
|
||||
req.ClientID = str
|
||||
}
|
||||
|
||||
func (req *UpdateWebHookReq) SetClientSecret(str string) {
|
||||
req.ClientSecret = str
|
||||
}
|
||||
|
||||
func (req *UpdateWebHookReq) GetGrantType() string {
|
||||
return req.GrantType
|
||||
}
|
||||
|
||||
func (req *UpdateWebHookReq) GetToken() string {
|
||||
return req.RefreshToken
|
||||
}
|
65
internal/models/getListFields.go
Normal file
65
internal/models/getListFields.go
Normal file
@ -0,0 +1,65 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/rs/xid"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type FieldsResponse struct {
|
||||
Result []Fields `json:"result"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
type Fields struct {
|
||||
ID string `json:"ID"`
|
||||
EntityID model.FieldsType `json:"ENTITY_ID"`
|
||||
FieldName string `json:"FIELD_NAME"`
|
||||
UserTypeID model.CustomFieldsType `json:"USER_TYPE_ID"`
|
||||
Sort string `json:"SORT"`
|
||||
Multiple string `json:"MULTIPLE"`
|
||||
Mandatory string `json:"MANDATORY"`
|
||||
ShowFilter string `json:"SHOW_FILTER"`
|
||||
ShowInList string `json:"SHOW_IN_LIST"`
|
||||
EditInList string `json:"EDIT_IN_LIST"`
|
||||
IsSearchable string `json:"IS_SEARCHABLE"`
|
||||
EditFormLabel string `json:"EDIT_FORM_LABEL"`
|
||||
ListColumnLabel string `json:"LIST_COLUMN_LABEL"`
|
||||
ListFilterLabel string `json:"LIST_FILTER_LABEL"`
|
||||
ErrorMessage string `json:"ERROR_MESSAGE"`
|
||||
HelpMessage string `json:"HELP_MESSAGE"`
|
||||
}
|
||||
|
||||
type AddFields struct {
|
||||
FieldName string `json:"FIELD_NAME"`
|
||||
EditFormLabel string `json:"EDIT_FORM_LABEL"`
|
||||
ListColumnLabel string `json:"LIST_COLUMN_LABEL"`
|
||||
UserTypeID model.CustomFieldsType `json:"USER_TYPE_ID"` // Тип поля
|
||||
XMLID string `json:"XML_ID"`
|
||||
Settings map[string]interface{} `json:"SETTINGS"`
|
||||
Mandatory string `json:"MANDATORY"`
|
||||
}
|
||||
|
||||
func (a *AddFields) GenFieldName() {
|
||||
guid := xid.New()
|
||||
guidGen := guid.String()
|
||||
// todo и так и так работает обдумать стоит
|
||||
// https://dev.1c-bitrix.ru/rest_help/crm/cdeals/crm_deal_userfield_add.php
|
||||
//a.FieldName = guidGen[:13]
|
||||
//a.XMLID = guidGen[:13]
|
||||
|
||||
a.FieldName = strings.ToUpper(guidGen)
|
||||
a.XMLID = strings.ToUpper(guidGen)
|
||||
a.Mandatory = "Y"
|
||||
}
|
||||
|
||||
type FileField struct {
|
||||
FileData []string `json:"fileData"`
|
||||
}
|
||||
|
||||
type UserFieldTypeAddReq struct {
|
||||
UserTypeID string `json:"USER_TYPE_ID"`
|
||||
Handler string `json:"HANDLER"`
|
||||
Title string `json:"TITLE"`
|
||||
Description string `json:"DESCRIPTION"`
|
||||
}
|
17
internal/models/getListPipelines.go
Normal file
17
internal/models/getListPipelines.go
Normal file
@ -0,0 +1,17 @@
|
||||
package models
|
||||
|
||||
import "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
|
||||
type Category struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Sort int32 `json:"sort"`
|
||||
EntityTypeId model.IntegerEntityType `json:"entityTypeId"`
|
||||
IsDefault model.BitrixIsDefault `json:"isDefault"`
|
||||
}
|
||||
|
||||
type CategoryResponse struct {
|
||||
Result struct {
|
||||
Categories []Category `json:"categories"`
|
||||
} `json:"result"`
|
||||
}
|
18
internal/models/getListSteps.go
Normal file
18
internal/models/getListSteps.go
Normal file
@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
type StepsResponse struct {
|
||||
Result []Steps `json:"result"`
|
||||
}
|
||||
|
||||
type Steps struct {
|
||||
ID string `json:"ID"`
|
||||
EntityID string `json:"ENTITY_ID"`
|
||||
StatusID string `json:"STATUS_ID"`
|
||||
Name string `json:"NAME"`
|
||||
NameInit string `json:"NAME_INIT,omitempty"`
|
||||
Sort string `json:"SORT"`
|
||||
System string `json:"SYSTEM"`
|
||||
Color string `json:"COLOR,omitempty"`
|
||||
Semantics string `json:"SEMANTICS,omitempty"`
|
||||
CategoryID string `json:"CATEGORY_ID"`
|
||||
}
|
21
internal/models/getUserList.go
Normal file
21
internal/models/getUserList.go
Normal file
@ -0,0 +1,21 @@
|
||||
package models
|
||||
|
||||
type ResponseGetListUsers struct {
|
||||
Result []User `json:"result"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID string `json:"ID"`
|
||||
Name string `json:"NAME"`
|
||||
LastName string `json:"LAST_NAME"`
|
||||
SecondName string `json:"SECOND_NAME"`
|
||||
Title string `json:"TITLE"`
|
||||
Email string `json:"EMAIL"`
|
||||
UFDepartment []int32 `json:"UF_DEPARTMENT"`
|
||||
WorkPosition string `json:"WORK_POSITION"`
|
||||
}
|
||||
|
||||
type ResponseGetCurrentUser struct {
|
||||
Result User `json:"result"`
|
||||
}
|
38
internal/models/kafkaMess.go
Normal file
38
internal/models/kafkaMess.go
Normal file
@ -0,0 +1,38 @@
|
||||
package models
|
||||
|
||||
import "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
|
||||
type KafkaMessage struct {
|
||||
AccountID string
|
||||
AuthCode string
|
||||
RefererURL string
|
||||
MemberID string
|
||||
Type MessageType
|
||||
Rule KafkaRule
|
||||
}
|
||||
|
||||
type KafkaRule struct {
|
||||
QuizID int32
|
||||
PerformerID string // айдишник ответственного за сделку
|
||||
PipelineID int32 // айдишник воронки
|
||||
TypeID string // шаг сделки только с "ENTITY_ID":"DEAL_TYPE","STATUS_ID":"SALE"
|
||||
StageID string // стадия сделки, шаг "ENTITY_ID":"DEAL_STAGE","STATUS_ID":"NEW"
|
||||
SourceID string // тип источника, шаг "ENTITY_ID":"SOURCE","STATUS_ID":"CALL"
|
||||
LeadFlag bool // флаг показывающий на то что нужен лид а не дил
|
||||
FieldsRule model.BitrixFieldRules
|
||||
StatusID string
|
||||
}
|
||||
|
||||
type MessageType string
|
||||
|
||||
const (
|
||||
UsersUpdate MessageType = "users"
|
||||
PipelinesUpdate MessageType = "pipelines"
|
||||
FieldsUpdate MessageType = "fields"
|
||||
TagsUpdate MessageType = "tags"
|
||||
UserCreate MessageType = "userCreate"
|
||||
AllDataUpdate MessageType = "allDataUpdate"
|
||||
RuleCheck MessageType = "ruleCheck"
|
||||
UserReLogin MessageType = "userReLogin"
|
||||
StepsUpdate MessageType = "steps"
|
||||
)
|
54
internal/models/reflect.go
Normal file
54
internal/models/reflect.go
Normal file
@ -0,0 +1,54 @@
|
||||
package models
|
||||
|
||||
import "reflect"
|
||||
|
||||
type FieldMapper interface {
|
||||
GetFields() interface{}
|
||||
}
|
||||
|
||||
func (c *CreatingDealReq) GetFields() interface{} {
|
||||
return c.Fields
|
||||
}
|
||||
|
||||
func (c *CompanyReq) GetFields() interface{} {
|
||||
return c.Fields
|
||||
}
|
||||
|
||||
func (c *CreateContactReq) GetFields() interface{} {
|
||||
return c.Fields
|
||||
}
|
||||
|
||||
func (c *CreatingLeadReq) GetFields() interface{} {
|
||||
return c.Fields
|
||||
}
|
||||
|
||||
func FormattingToMap(f FieldMapper, fieldAnswer map[string]string) map[string]map[string]interface{} {
|
||||
resultFields := make(map[string]interface{})
|
||||
result := make(map[string]map[string]interface{})
|
||||
|
||||
fields := reflect.ValueOf(f.GetFields())
|
||||
fieldType := reflect.TypeOf(f.GetFields())
|
||||
|
||||
for i := 0; i < fields.NumField(); i++ {
|
||||
field := fields.Field(i)
|
||||
fieldName := fieldType.Field(i).Tag.Get("json")
|
||||
|
||||
switch field.Kind() {
|
||||
case reflect.String:
|
||||
resultFields[fieldName] = field.String()
|
||||
case reflect.Int32, reflect.Int:
|
||||
resultFields[fieldName] = field.Int()
|
||||
case reflect.Slice:
|
||||
if field.Type().Elem().Kind() == reflect.Int32 {
|
||||
resultFields[fieldName] = field.Interface().([]int32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range fieldAnswer {
|
||||
resultFields[key] = value
|
||||
}
|
||||
result["fields"] = resultFields
|
||||
|
||||
return result
|
||||
}
|
66
internal/server/http.go
Normal file
66
internal/server/http.go
Normal file
@ -0,0 +1,66 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware"
|
||||
)
|
||||
|
||||
type ServerConfig struct {
|
||||
Controllers []Controller
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Controllers []Controller
|
||||
app *fiber.App
|
||||
}
|
||||
|
||||
func NewServer(config ServerConfig) *Server {
|
||||
app := fiber.New()
|
||||
app.Use("/bitrix", middleware.JWTAuth())
|
||||
app.Use("/webhook", func(c *fiber.Ctx) error {
|
||||
return c.Next()
|
||||
})
|
||||
|
||||
s := &Server{
|
||||
Controllers: config.Controllers,
|
||||
app: app,
|
||||
}
|
||||
|
||||
s.registerRoutes()
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Server) Start(addr string) error {
|
||||
if err := s.app.Listen(addr); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Shutdown(ctx context.Context) error {
|
||||
return s.app.Shutdown()
|
||||
}
|
||||
|
||||
func (s *Server) registerRoutes() {
|
||||
for _, c := range s.Controllers {
|
||||
router := s.app.Group(c.Name())
|
||||
c.Register(router)
|
||||
}
|
||||
}
|
||||
|
||||
type Controller interface {
|
||||
Register(router fiber.Router)
|
||||
Name() string
|
||||
}
|
||||
|
||||
func (s *Server) ListRoutes() {
|
||||
fmt.Println("Registered routes:")
|
||||
for _, stack := range s.app.Stack() {
|
||||
for _, route := range stack {
|
||||
fmt.Printf("%s %s\n", route.Method, route.Path)
|
||||
}
|
||||
}
|
||||
}
|
38
internal/service/fields.go
Normal file
38
internal/service/fields.go
Normal file
@ -0,0 +1,38 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/tools"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
|
||||
)
|
||||
|
||||
func (s *Service) GetFieldsWithPagination(ctx context.Context, req *model.PaginationReq, accountID string) (*model.UserListBitrixFieldsResp, error) {
|
||||
response, err := s.repository.BitrixRepo.GetFieldsWithPagination(ctx, req, accountID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, pj_errors.ErrNotFound
|
||||
}
|
||||
s.logger.Error("error getting fields with pagination", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return tools.ValidateUtmFields(response), nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateListCustom(ctx context.Context, accountID string) error {
|
||||
message := models.KafkaMessage{
|
||||
AccountID: accountID,
|
||||
Type: models.FieldsUpdate,
|
||||
}
|
||||
|
||||
err := s.producer.ToKafkaUpdate(ctx, message)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to send message to kafka on service update fields", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
39
internal/service/initial.go
Normal file
39
internal/service/initial.go
Normal file
@ -0,0 +1,39 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"gitea.pena/PenaSide/common/encrypt"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/brokers"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/initialize"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/pkg/bitrixClient"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||
)
|
||||
|
||||
type Deps struct {
|
||||
Repository *dal.BitrixDal
|
||||
Logger *zap.Logger
|
||||
BitrixClient *bitrixClient.Bitrix
|
||||
Producer *brokers.Producer
|
||||
Config initialize.Config
|
||||
Encrypt *encrypt.Encrypt
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
repository *dal.BitrixDal
|
||||
logger *zap.Logger
|
||||
bitrixClient *bitrixClient.Bitrix
|
||||
producer *brokers.Producer
|
||||
config initialize.Config
|
||||
encrypt *encrypt.Encrypt
|
||||
}
|
||||
|
||||
func NewService(deps Deps) *Service {
|
||||
return &Service{
|
||||
repository: deps.Repository,
|
||||
logger: deps.Logger,
|
||||
bitrixClient: deps.BitrixClient,
|
||||
producer: deps.Producer,
|
||||
config: deps.Config,
|
||||
encrypt: deps.Encrypt,
|
||||
}
|
||||
}
|
37
internal/service/pipelines.go
Normal file
37
internal/service/pipelines.go
Normal file
@ -0,0 +1,37 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
|
||||
)
|
||||
|
||||
func (s *Service) UpdateListPipelines(ctx context.Context, accountID string) error {
|
||||
message := models.KafkaMessage{
|
||||
AccountID: accountID,
|
||||
Type: models.PipelinesUpdate,
|
||||
}
|
||||
|
||||
err := s.producer.ToKafkaUpdate(ctx, message)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to send message to kafka on service update pipelines", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GetPipelinesWithPagination(ctx context.Context, req *model.PaginationReq, accountID string) (*model.UserBitrixListPipelinesResp, error) {
|
||||
response, err := s.repository.BitrixRepo.GetPipelinesWithPagination(ctx, req, accountID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, pj_errors.ErrNotFound
|
||||
}
|
||||
s.logger.Error("error getting pipelines with pagination", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
96
internal/service/rules.go
Normal file
96
internal/service/rules.go
Normal file
@ -0,0 +1,96 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
|
||||
)
|
||||
|
||||
func (s *Service) ChangeQuizSettings(ctx context.Context, request *model.BitrixRulesReq, accountID string, quizID int) error {
|
||||
err := s.repository.BitrixRepo.ChangeQuizSettings(ctx, request, accountID, quizID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return pj_errors.ErrNotFound
|
||||
}
|
||||
s.logger.Error("error change quiz settings", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
messageForUTM := models.KafkaMessage{
|
||||
AccountID: accountID,
|
||||
Type: models.RuleCheck,
|
||||
Rule: models.KafkaRule{
|
||||
QuizID: int32(quizID),
|
||||
PerformerID: request.PerformerID,
|
||||
PipelineID: request.PipelineID,
|
||||
TypeID: request.TypeID,
|
||||
StageID: request.StageID,
|
||||
SourceID: request.SourceID,
|
||||
FieldsRule: request.FieldsRule,
|
||||
LeadFlag: request.LeadFlag,
|
||||
StatusID: request.StatusID,
|
||||
},
|
||||
}
|
||||
|
||||
err = s.producer.ToKafkaUpdate(ctx, messageForUTM)
|
||||
if err != nil {
|
||||
s.logger.Error("error sending message to kafka for check rules", zap.Error(err))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SetQuizSettings(ctx context.Context, request *model.BitrixRulesReq, accountID string, quizID int) error {
|
||||
err := s.repository.BitrixRepo.SetQuizSettings(ctx, request, accountID, quizID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return pj_errors.ErrNotFound
|
||||
}
|
||||
s.logger.Error("error setting quiz settings", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
messageForUTM := models.KafkaMessage{
|
||||
AccountID: accountID,
|
||||
Type: models.RuleCheck,
|
||||
Rule: models.KafkaRule{
|
||||
QuizID: int32(quizID),
|
||||
PerformerID: request.PerformerID,
|
||||
PipelineID: request.PipelineID,
|
||||
TypeID: request.TypeID,
|
||||
StageID: request.StageID,
|
||||
SourceID: request.SourceID,
|
||||
FieldsRule: request.FieldsRule,
|
||||
LeadFlag: request.LeadFlag,
|
||||
StatusID: request.StatusID,
|
||||
},
|
||||
}
|
||||
|
||||
err = s.producer.ToKafkaUpdate(ctx, messageForUTM)
|
||||
if err != nil {
|
||||
s.logger.Error("error sending message to kafka for check rules", zap.Error(err))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GettingQuizRules(ctx context.Context, quizID int) (*model.BitrixRule, error) {
|
||||
rule, err := s.repository.BitrixRepo.GettingQuizRules(ctx, quizID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, pj_errors.ErrNotFound
|
||||
}
|
||||
s.logger.Error("error getting quiz settings", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return rule, nil
|
||||
}
|
37
internal/service/steps.go
Normal file
37
internal/service/steps.go
Normal file
@ -0,0 +1,37 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
|
||||
)
|
||||
|
||||
func (s *Service) GetStepsWithPagination(ctx context.Context, req *model.PaginationReq, accountID string) (*model.UserListBitrixStepsResp, error) {
|
||||
response, err := s.repository.BitrixRepo.GetStepsWithPagination(ctx, req, accountID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, pj_errors.ErrNotFound
|
||||
}
|
||||
s.logger.Error("error getting steps with pagination", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateListSteps(ctx context.Context, accountID string) error {
|
||||
message := models.KafkaMessage{
|
||||
AccountID: accountID,
|
||||
Type: models.StepsUpdate,
|
||||
}
|
||||
|
||||
err := s.producer.ToKafkaUpdate(ctx, message)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to send message to kafka on service update steps", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
28
internal/service/tags.go
Normal file
28
internal/service/tags.go
Normal file
@ -0,0 +1,28 @@
|
||||
package service
|
||||
|
||||
//func (s *Service) GetTagsWithPagination(ctx context.Context, req *model.PaginationReq, accountID string) (*model.UserListTagsResp, error) {
|
||||
// response, err := s.repository.BitrixRepo.GetTagsWithPagination(ctx, req, accountID)
|
||||
// if err != nil {
|
||||
// if err == sql.ErrNoRows {
|
||||
// return nil, pj_errors.ErrNotFound
|
||||
// }
|
||||
// s.logger.Error("error getting tags with pagination", zap.Error(err))
|
||||
// return nil, err
|
||||
// }
|
||||
// return response, nil
|
||||
//}
|
||||
//
|
||||
//func (s *Service) UpdateListTags(ctx context.Context, accountID string) error {
|
||||
// message := models.KafkaMessage{
|
||||
// AccountID: accountID,
|
||||
// Type: models.TagsUpdate,
|
||||
// }
|
||||
//
|
||||
// err := s.producer.ToKafkaUpdate(ctx, message)
|
||||
// if err != nil {
|
||||
// s.logger.Error("failed to send message to kafka on service update tags", zap.Error(err))
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
//}
|
81
internal/service/user.go
Normal file
81
internal/service/user.go
Normal file
@ -0,0 +1,81 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"go.uber.org/zap"
|
||||
"net/url"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
|
||||
)
|
||||
|
||||
func (s *Service) UpdateListUsers(ctx context.Context, accountID string) error {
|
||||
message := models.KafkaMessage{
|
||||
AccountID: accountID,
|
||||
Type: models.UsersUpdate,
|
||||
}
|
||||
|
||||
err := s.producer.ToKafkaUpdate(ctx, message)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to send message to kafka on service update users", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GettingUserWithPagination(ctx context.Context, req *model.PaginationReq, accountID string) (*model.UserListBitrixResp, error) {
|
||||
response, err := s.repository.BitrixRepo.GettingUserWithPagination(ctx, req, accountID)
|
||||
if err != nil {
|
||||
s.logger.Error("error getting users with pagination", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *Service) SoftDeleteAccount(ctx context.Context, accountID string) error {
|
||||
err := s.repository.BitrixRepo.SoftDeleteAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
s.logger.Error("error soft delete current account in softDeleteAccount service", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GetCurrentAccount(ctx context.Context, accountID string) (*model.BitrixAccount, error) {
|
||||
user, err := s.repository.BitrixRepo.GetCurrentAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, pj_errors.ErrNotFound
|
||||
}
|
||||
s.logger.Error("error getting current account in getCurrentAccount service", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *Service) ConnectAccount(ctx context.Context, accountID string) (*model.ConnectAccountResp, error) {
|
||||
state, err := s.encrypt.EncryptStr(accountID)
|
||||
if err != nil {
|
||||
s.logger.Error("error encrypting account state", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oauthURL := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "b24-s5jg6c.bitrix24.ru", // todo check надо проверить как с дургими доменами работает, потому что сейчас это домен каждого отдельного битрикса
|
||||
Path: "/oauth/authorize/",
|
||||
RawQuery: url.Values{
|
||||
"client_id": {s.config.BitrixIntegrationID},
|
||||
"state": {string(state)},
|
||||
}.Encode(),
|
||||
}
|
||||
|
||||
response := model.ConnectAccountResp{
|
||||
Link: oauthURL.String(),
|
||||
}
|
||||
|
||||
return &response, nil
|
||||
}
|
67
internal/service/webhook.go
Normal file
67
internal/service/webhook.go
Normal file
@ -0,0 +1,67 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
|
||||
)
|
||||
|
||||
type ParamsWebhookCreate struct {
|
||||
Code string
|
||||
Domain string
|
||||
AccountID string
|
||||
MemberID string
|
||||
Scope string
|
||||
ServerDomain string
|
||||
}
|
||||
|
||||
func (s *Service) WebhookCreate(ctx context.Context, req ParamsWebhookCreate) error {
|
||||
_, err := s.GetCurrentAccount(ctx, req.AccountID)
|
||||
if err != nil && !errors.Is(err, pj_errors.ErrNotFound) {
|
||||
s.logger.Error("error checking current account in bitrix in webhook create", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if errors.Is(err, pj_errors.ErrNotFound) {
|
||||
message := models.KafkaMessage{
|
||||
AccountID: req.AccountID,
|
||||
AuthCode: req.Code,
|
||||
RefererURL: req.Domain,
|
||||
MemberID: req.MemberID,
|
||||
Type: models.UserCreate,
|
||||
}
|
||||
|
||||
err = s.producer.ToKafkaUpdate(ctx, message)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to send message to kafka on service webhook create", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
message := models.KafkaMessage{
|
||||
AccountID: req.AccountID,
|
||||
AuthCode: req.Code,
|
||||
RefererURL: req.Domain,
|
||||
Type: models.UserReLogin,
|
||||
}
|
||||
|
||||
err = s.producer.ToKafkaUpdate(ctx, message)
|
||||
if err != nil {
|
||||
s.logger.Error("failed to send message to kafka on service webhook create, user re-login", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//func (s *Service) WebhookDelete(ctx context.Context, bitrixID int) error {
|
||||
// err := s.repository.BitrixRepo.WebhookDelete(ctx, bitrixID)
|
||||
// if err != nil {
|
||||
// s.logger.Error("error canceled bitrix integration", zap.Error(err))
|
||||
// return err
|
||||
// }
|
||||
// return nil
|
||||
//}
|
104
internal/tools/construct.go
Normal file
104
internal/tools/construct.go
Normal file
@ -0,0 +1,104 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func ToPipeline(bitrixPipelines []models.Category, bitrixID string) []model.PipelineBitrix {
|
||||
var pipelines []model.PipelineBitrix
|
||||
for _, p := range bitrixPipelines {
|
||||
pipelines = append(pipelines, model.PipelineBitrix{
|
||||
BitrixID: p.ID,
|
||||
Name: p.Name,
|
||||
EntityTypeId: p.EntityTypeId,
|
||||
AccountID: bitrixID,
|
||||
})
|
||||
}
|
||||
return pipelines
|
||||
}
|
||||
|
||||
func ToStep(bitrixPipelines []models.Steps, bitrixID string) ([]model.StepBitrix, error) {
|
||||
var pipelines []model.StepBitrix
|
||||
for _, p := range bitrixPipelines {
|
||||
pipelineID, err := strconv.ParseInt(p.ID, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pipelines = append(pipelines, model.StepBitrix{
|
||||
BitrixID: p.ID,
|
||||
AccountID: bitrixID,
|
||||
EntityID: p.EntityID,
|
||||
StatusID: p.StatusID,
|
||||
Name: p.Name,
|
||||
NameInit: p.NameInit,
|
||||
Color: p.Color,
|
||||
PipelineID: int32(pipelineID),
|
||||
})
|
||||
}
|
||||
return pipelines, nil
|
||||
}
|
||||
|
||||
func ToField(bitrixFields []models.Fields, bitrixID string) []model.BitrixField {
|
||||
var fields []model.BitrixField
|
||||
for _, f := range bitrixFields {
|
||||
fields = append(fields, model.BitrixField{
|
||||
AccountID: bitrixID,
|
||||
BitrixID: f.ID,
|
||||
EntityID: f.EntityID,
|
||||
FieldName: f.FieldName,
|
||||
EditFromLabel: f.EditFormLabel,
|
||||
FieldType: f.UserTypeID,
|
||||
})
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
func isEmoji(r rune) bool {
|
||||
// https://symbl.cc/ru/unicode/blocks/emoticons/
|
||||
|
||||
return (r >= 0x1F600 && r <= 0x1F64F) || // эмотикоины
|
||||
(r >= 0x1F650 && r <= 0x1F67F) || // орнаментные символы
|
||||
(r >= 0x1F680 && r <= 0x1F6FF) || // Транспортные и картографические символы
|
||||
(r >= 0x1F700 && r <= 0x1F77F) || // Алхимические символы
|
||||
(r >= 0x1F780 && r <= 0x1F7FF) || // Расширенные геометрические фигуры
|
||||
(r >= 0x1F800 && r <= 0x1F8FF) || // Дополнительные стрелки — С
|
||||
(r >= 0x1F900 && r <= 0x1F9FF) || // Дополнительные символы и пиктограммы
|
||||
(r >= 0x1FA00 && r <= 0x1FA6F) || // Шахматные символы
|
||||
(r >= 0x1FA70 && r <= 0x1FAFF) || // Расширенные символы и пиктограммы — A
|
||||
(r >= 0x1FB00 && r <= 0x1FBFF) || // Символы наследия вычислительной техники
|
||||
(r >= 0x20000 && r <= 0x2A6DF) || // Унифицированные идеограммы ККЯ. Расширение B
|
||||
(r >= 0x2A700 && r <= 0x2B73F) || // Унифицированные идеограммы ККЯ. Расширение C
|
||||
(r >= 0x2B740 && r <= 0x2B81F) || // Унифицированные идеограммы ККЯ. Расширение D
|
||||
(r >= 0x2B820 && r <= 0x2CEAF) || // Унифицированные идеограммы ККЯ. Расширение E
|
||||
(r >= 0x2CEB0 && r <= 0x2EBEF) || // Унифицированные идеограммы ККЯ. Расширение F
|
||||
(r >= 0x2EBF0 && r <= 0x2EE5F) || // CJK Unified Ideographs Extension I
|
||||
(r >= 0x2F800 && r <= 0x2FA1F) || // Дополнение к совместимым идеограммам ККЯ
|
||||
(r >= 0x30000 && r <= 0x3134F) || // Унифицированные идеограммы ККЯ. Расширение G
|
||||
(r >= 0x31350 && r <= 0x323AF) || // Унифицированные идеограммы ККЯ. Расширение H
|
||||
(r >= 0xE0000 && r <= 0xE007F) || // Теги
|
||||
(r >= 0xE0100 && r <= 0xE01EF) // Дополнение к селекторам вариантов начертания
|
||||
}
|
||||
|
||||
func EmojiUnicode(text string) string {
|
||||
var result strings.Builder
|
||||
for len(text) > 0 {
|
||||
r, size := utf8.DecodeRuneInString(text)
|
||||
if size == -1 {
|
||||
result.WriteString(text[:1])
|
||||
text = text[1:]
|
||||
} else {
|
||||
if isEmoji(r) {
|
||||
result.WriteString(fmt.Sprintf(`"0x%x"`, r))
|
||||
} else {
|
||||
result.WriteRune(r)
|
||||
}
|
||||
text = text[size:]
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
106
internal/tools/forRules.go
Normal file
106
internal/tools/forRules.go
Normal file
@ -0,0 +1,106 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ToQuestionIDs(rule map[int]string) []int32 {
|
||||
var ids []int32
|
||||
for queID, fieldID := range rule {
|
||||
if fieldID != "" {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, int32(queID))
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func ForContactRules(quizConfig model.QuizContact, currentFields []model.BitrixField) ([]models.AddFields, map[string]string) {
|
||||
var contactFieldsArr []model.ContactQuizConfig
|
||||
contactFieldTypes := map[model.ContactQuizConfig]bool{
|
||||
model.TypeContactName: quizConfig.FormContact.Fields.Name.Used,
|
||||
model.TypeContactEmail: quizConfig.FormContact.Fields.Email.Used,
|
||||
model.TypeContactPhone: quizConfig.FormContact.Fields.Phone.Used,
|
||||
model.TypeContactText: quizConfig.FormContact.Fields.Text.Used,
|
||||
model.TypeContactAddress: quizConfig.FormContact.Fields.Address.Used,
|
||||
}
|
||||
|
||||
for fieldType, used := range contactFieldTypes {
|
||||
if used {
|
||||
contactFieldsArr = append(contactFieldsArr, fieldType)
|
||||
}
|
||||
}
|
||||
|
||||
forAdding := make(map[string]string)
|
||||
var toCreated []models.AddFields
|
||||
for _, contactField := range contactFieldsArr {
|
||||
matched := false
|
||||
for _, field := range currentFields {
|
||||
if field.EditFromLabel == string(contactField) && field.EntityID == model.FieldTypeContact {
|
||||
matched = true
|
||||
forAdding[string(contactField)] = field.BitrixID
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !matched {
|
||||
toCreated = append(toCreated, models.AddFields{
|
||||
UserTypeID: model.StringCustomFieldsType,
|
||||
EditFormLabel: string(contactField),
|
||||
ListColumnLabel: string(contactField),
|
||||
})
|
||||
forAdding[string(contactField)] = ""
|
||||
}
|
||||
}
|
||||
|
||||
return toCreated, forAdding
|
||||
}
|
||||
|
||||
type ToUpdate struct {
|
||||
FieldID string
|
||||
Entity model.FieldsType
|
||||
}
|
||||
|
||||
func ToCreatedUpdateQuestionRules(questionsTypeMap map[model.FieldsType][]model.Question, currentFields []model.BitrixField) (map[model.FieldsType][]models.AddFields, map[int]ToUpdate) {
|
||||
toUpdate := make(map[int]ToUpdate) // на обновление ключ id вопроса значение id кастомного поля для тех у кого имя совпадает
|
||||
toCreated := make(map[model.FieldsType][]models.AddFields)
|
||||
for entity, questions := range questionsTypeMap {
|
||||
for _, question := range questions {
|
||||
// если заголоввок пустой у вопроса делаем ему заголовок чтоб в амо легли филды нормально
|
||||
title := strings.ToLower(strings.ReplaceAll(question.Title, " ", ""))
|
||||
if title == "" {
|
||||
question.Title = fmt.Sprintf("Вопрос №%d", question.Page)
|
||||
}
|
||||
title = strings.ToLower(strings.ReplaceAll(question.Title, " ", ""))
|
||||
matched := false
|
||||
for _, field := range currentFields {
|
||||
fieldName := strings.ToLower(strings.ReplaceAll(field.EditFromLabel, " ", ""))
|
||||
if title == fieldName && entity == field.EntityID {
|
||||
toUpdate[int(question.Id)] = ToUpdate{
|
||||
FieldID: field.BitrixID,
|
||||
Entity: entity,
|
||||
}
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !matched {
|
||||
fieldType := model.StringCustomFieldsType
|
||||
if question.Type == model.TypeFile {
|
||||
fieldType = model.FileCustomFieldsType
|
||||
}
|
||||
toCreated[entity] = append(toCreated[entity], models.AddFields{
|
||||
UserTypeID: fieldType,
|
||||
EditFormLabel: question.Title,
|
||||
ListColumnLabel: question.Title,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return toCreated, toUpdate
|
||||
}
|
42
internal/tools/validate.go
Normal file
42
internal/tools/validate.go
Normal file
@ -0,0 +1,42 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
)
|
||||
|
||||
func ValidateUtmFields(response *model.UserListBitrixFieldsResp) *model.UserListBitrixFieldsResp {
|
||||
checkUTM := map[string]struct{}{
|
||||
"utm_content": {},
|
||||
"utm_medium": {},
|
||||
"utm_campaign": {},
|
||||
"utm_source": {},
|
||||
"utm_term": {},
|
||||
"utm_referrer": {},
|
||||
"roistat": {},
|
||||
"referrer": {},
|
||||
"openstat_service": {},
|
||||
"openstat_campaign": {},
|
||||
"openstat_ad": {},
|
||||
"openstat_source": {},
|
||||
"from": {},
|
||||
"gclientid": {},
|
||||
"_ym_uid": {},
|
||||
"_ym_counter": {},
|
||||
"gclid": {},
|
||||
"yclid": {},
|
||||
"fbclid": {},
|
||||
}
|
||||
|
||||
data := &model.UserListBitrixFieldsResp{
|
||||
Count: response.Count,
|
||||
Items: []model.BitrixField{},
|
||||
}
|
||||
|
||||
for _, r := range response.Items {
|
||||
if _, ok := checkUTM[r.EditFromLabel]; !ok {
|
||||
data.Items = append(data.Items, r)
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
56
internal/workers/data_updater/data_updater.go
Normal file
56
internal/workers/data_updater/data_updater.go
Normal file
@ -0,0 +1,56 @@
|
||||
package data_updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/brokers"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Deps struct {
|
||||
Logger *zap.Logger
|
||||
Producer *brokers.Producer
|
||||
}
|
||||
|
||||
type DataUpdater struct {
|
||||
logger *zap.Logger
|
||||
producer *brokers.Producer
|
||||
}
|
||||
|
||||
func NewDataUpdaterWC(deps Deps) *DataUpdater {
|
||||
return &DataUpdater{
|
||||
logger: deps.Logger,
|
||||
producer: deps.Producer,
|
||||
}
|
||||
}
|
||||
|
||||
// раз в час так как акес токен живет час
|
||||
func (wc *DataUpdater) Start(ctx context.Context) {
|
||||
ticker := time.NewTicker(time.Hour)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
wc.processTasks(ctx)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wc *DataUpdater) processTasks(ctx context.Context) {
|
||||
err := wc.producer.ToKafkaUpdate(ctx, models.KafkaMessage{
|
||||
Type: models.AllDataUpdate,
|
||||
AccountID: "",
|
||||
})
|
||||
if err != nil {
|
||||
wc.logger.Error("error send task in kafka ro update all data", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (wc *DataUpdater) Stop(_ context.Context) error {
|
||||
return nil
|
||||
}
|
58
internal/workers/limiter/limiter.go
Normal file
58
internal/workers/limiter/limiter.go
Normal file
@ -0,0 +1,58 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RateLimiter struct {
|
||||
requests chan struct{}
|
||||
done chan struct{}
|
||||
maxRequests int
|
||||
Interval time.Duration
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewRateLimiter(ctx context.Context, maxRequests int, interval time.Duration) *RateLimiter {
|
||||
limiter := &RateLimiter{
|
||||
requests: make(chan struct{}, maxRequests),
|
||||
done: make(chan struct{}),
|
||||
maxRequests: maxRequests,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
go limiter.start(ctx)
|
||||
return limiter
|
||||
}
|
||||
|
||||
func (limiter *RateLimiter) start(ctx context.Context) {
|
||||
ticker := time.NewTicker(limiter.Interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
limiter.mutex.Lock()
|
||||
for i := 0; i < len(limiter.requests); i++ {
|
||||
<-limiter.requests
|
||||
}
|
||||
limiter.mutex.Unlock()
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (limiter *RateLimiter) Check() bool {
|
||||
select {
|
||||
case limiter.requests <- struct{}{}:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (limiter *RateLimiter) Stop(_ context.Context) error {
|
||||
return nil
|
||||
}
|
799
internal/workers/post_deals_worker/deals_worker.go
Normal file
799
internal/workers/post_deals_worker/deals_worker.go
Normal file
@ -0,0 +1,799 @@
|
||||
package post_deals_worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/tools"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/pkg/bitrixClient"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/repository/bitrix"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/utils"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Deps struct {
|
||||
BitrixRepo *dal.BitrixDal
|
||||
BitrixClient *bitrixClient.Bitrix
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
type DealsWorker struct {
|
||||
bitrixRepo *dal.BitrixDal
|
||||
bitrixClient *bitrixClient.Bitrix
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewDealsWC(deps Deps) *DealsWorker {
|
||||
return &DealsWorker{
|
||||
bitrixRepo: deps.BitrixRepo,
|
||||
bitrixClient: deps.BitrixClient,
|
||||
logger: deps.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (wc *DealsWorker) Start(ctx context.Context) {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
wc.startFetching(ctx)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wc *DealsWorker) startFetching(ctx context.Context) {
|
||||
results, err := wc.bitrixRepo.BitrixRepo.GettingBitrixUsersTrueResults(ctx)
|
||||
if err != nil {
|
||||
wc.logger.Error("failed to fetch bitrix users true-results", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
userPrivileges, err := wc.bitrixRepo.AccountRepo.GetPrivilegesByAccountID(ctx, result.QuizAccountID)
|
||||
if err != nil {
|
||||
wc.logger.Error("error getting user privileges", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.VerifyUserPrivileges(userPrivileges) {
|
||||
wc.logger.Info("User don't have active quizCnt or quizUnlim privileges, aborting")
|
||||
continue
|
||||
}
|
||||
|
||||
allAnswers, err := wc.bitrixRepo.AnswerRepo.GetAllAnswersByQuizID(ctx, result.Session)
|
||||
if err != nil {
|
||||
wc.logger.Error("error getting all user answers by result session", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// todo
|
||||
//userTags, err := wc.amoRepo.AmoRepo.GetUserTagsByID(ctx, result.AmoAccountID)
|
||||
//if err != nil {
|
||||
// wc.logger.Error("error getting user tags by ano account id", zap.Error(err))
|
||||
// return
|
||||
//}'
|
||||
|
||||
performerIDInt, err := strconv.Atoi(result.PerformerID)
|
||||
if err != nil {
|
||||
wc.logger.Error("error parsing performerID", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
var request interface{}
|
||||
|
||||
if result.LeadFlag {
|
||||
request = models.CreatingLeadReq{
|
||||
Fields: models.CreateLeadFields{
|
||||
Title: fmt.Sprintf("lead quiz number %d", result.QuizID),
|
||||
Opened: "Y",
|
||||
AssignedByID: performerIDInt,
|
||||
SourceID: result.SourceID,
|
||||
StatusID: result.StatusID,
|
||||
UtmSource: result.UTMs["utm_source"],
|
||||
UtmCampaign: result.UTMs["utm_campaign"],
|
||||
UtmContent: result.UTMs["utm_content"],
|
||||
UtmMedium: result.UTMs["utm_medium"],
|
||||
UtmTerm: result.UTMs["utm_term"],
|
||||
},
|
||||
}
|
||||
} else {
|
||||
request = models.CreatingDealReq{
|
||||
Fields: models.CreateDealFields{
|
||||
Title: fmt.Sprintf("deal quiz number %d", result.QuizID),
|
||||
TypeID: result.TypeID,
|
||||
StageID: result.StageID,
|
||||
Opened: "Y",
|
||||
AssignedByID: performerIDInt,
|
||||
CategoryID: result.PipelineID,
|
||||
SourceID: result.SourceID,
|
||||
UtmSource: result.UTMs["utm_source"],
|
||||
UtmCampaign: result.UTMs["utm_campaign"],
|
||||
UtmContent: result.UTMs["utm_content"],
|
||||
UtmMedium: result.UTMs["utm_medium"],
|
||||
UtmTerm: result.UTMs["utm_term"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
reqMap, err := wc.constructField(ctx, request, allAnswers, result, performerIDInt)
|
||||
if err != nil {
|
||||
wc.logger.Error("error construct fields", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
wc.logger.Info("NOW DEAL/LEAD CONSTRUCTED IS:", zap.Any("DEAL", reqMap))
|
||||
|
||||
var errCreating error
|
||||
var id int32
|
||||
|
||||
if result.LeadFlag {
|
||||
id, errCreating = wc.bitrixClient.CreatingLead(reqMap, result.AccessToken, result.SubDomain)
|
||||
if errCreating != nil {
|
||||
wc.logger.Error("error creating lead", zap.Error(errCreating))
|
||||
}
|
||||
|
||||
} else {
|
||||
id, errCreating = wc.bitrixClient.CreatingDeal(reqMap, result.AccessToken, result.SubDomain)
|
||||
if errCreating != nil {
|
||||
wc.logger.Error("error creating deal", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
if errCreating != nil {
|
||||
err = wc.bitrixRepo.BitrixRepo.SaveDealBitrixStatus(ctx, bitrix.SaveDealBitrixDeps{
|
||||
DealID: 0,
|
||||
AnswerID: result.AnswerID,
|
||||
AccessToken: result.AccessToken,
|
||||
Status: err.Error(),
|
||||
})
|
||||
if err != nil {
|
||||
wc.logger.Error("error saving deal status", zap.Error(err))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("DEALID", id)
|
||||
|
||||
err = wc.bitrixRepo.BitrixRepo.SaveDealBitrixStatus(ctx, bitrix.SaveDealBitrixDeps{
|
||||
DealID: id,
|
||||
AnswerID: result.AnswerID,
|
||||
AccessToken: result.AccessToken,
|
||||
Status: "OK",
|
||||
})
|
||||
if err != nil {
|
||||
wc.logger.Error("error saving deal status", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo ОБДУМАТЬ ЛИДЫ ПОЧЕМУ ЛИД ПРЕВРАЩАЕТСЯ В СДЕЛКУ
|
||||
func (wc *DealsWorker) constructField(ctx context.Context, request interface{}, allAnswers []model.ResultAnswer, result model.BitrixUsersTrueResults, performerIDInt int) (map[string]map[string]interface{}, error) {
|
||||
// id поля - ответ по типу поля
|
||||
entityFieldsMap := make(map[model.FieldsType]map[string]string)
|
||||
entityFieldsMap[model.FieldTypeDeal] = make(map[string]string)
|
||||
entityFieldsMap[model.FieldTypeLead] = make(map[string]string)
|
||||
entityFieldsMap[model.FieldTypeCompany] = make(map[string]string)
|
||||
entityFieldsMap[model.FieldTypeContact] = make(map[string]string)
|
||||
|
||||
// тип поля = правила на этот тип
|
||||
entityRules := make(map[model.FieldsType]map[int]string)
|
||||
entityRules[model.FieldTypeDeal] = result.FieldsRule.Deal.QuestionID
|
||||
entityRules[model.FieldTypeLead] = result.FieldsRule.Lead.QuestionID
|
||||
entityRules[model.FieldTypeCompany] = result.FieldsRule.Company.QuestionID
|
||||
entityRules[model.FieldTypeContact] = result.FieldsRule.Contact.QuestionID
|
||||
|
||||
fmt.Println("result.FieldsRule.Deal.QuestionID", result.FieldsRule.Deal.QuestionID)
|
||||
fmt.Println("result.FieldsRule.Lead.QuestionID", result.FieldsRule.Lead.QuestionID)
|
||||
|
||||
for entityType, rule := range entityRules {
|
||||
for _, data := range allAnswers {
|
||||
if fieldID, ok := rule[int(data.QuestionID)]; ok {
|
||||
fieldData, err := wc.bitrixRepo.BitrixRepo.GetFieldByID(ctx, fieldID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
wc.logger.Info("This field id does not exist in db", zap.Any("fieldID", fieldID))
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fieldData.FieldType == model.FileCustomFieldsType {
|
||||
continue
|
||||
}
|
||||
|
||||
//if entityType != model.FieldTypeDeal {
|
||||
if fieldData.FieldType == model.StringCustomFieldsType {
|
||||
content := strings.ReplaceAll(data.Content, " ", "")
|
||||
if content == "" {
|
||||
data.Content = "Пустая строка"
|
||||
}
|
||||
entityFieldsMap[entityType][fieldData.FieldName] = tools.EmojiUnicode(data.Content)
|
||||
continue
|
||||
}
|
||||
//}
|
||||
// todo need test // "https://storage.yandexcloud.net/squizanswer/5873c188-9ece-4540-96d8-4c3cb451a13f/109965/coq1ei7ot84c73fnk5cg.pdf"
|
||||
//fieldData.FieldType = model.FileCustomFieldsType
|
||||
if fieldData.FieldType == model.FileCustomFieldsType && data.Content != "" {
|
||||
fileName, base64Data, err := wc.bitrixClient.DownLoadFile(data.Content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileEntry := models.FileField{
|
||||
FileData: []string{fileName, base64Data},
|
||||
}
|
||||
|
||||
fileEntryJson, err := json.Marshal(fileEntry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//entityFieldsMap[entityType]["UF_CRM_1729686490"] = string(fileEntryJson)
|
||||
entityFieldsMap[entityType][fieldData.FieldName] = string(fileEntryJson)
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contactFields := entityFieldsMap[model.FieldTypeContact]
|
||||
companyFields := entityFieldsMap[model.FieldTypeCompany]
|
||||
|
||||
dealFields := make(map[string]string)
|
||||
var dealReq models.CreatingDealReq
|
||||
leadFields := make(map[string]string)
|
||||
var leadReq models.CreatingLeadReq
|
||||
|
||||
if result.LeadFlag {
|
||||
leadReq = request.(models.CreatingLeadReq)
|
||||
leadFields = entityFieldsMap[model.FieldTypeLead]
|
||||
} else {
|
||||
dealFields = entityFieldsMap[model.FieldTypeDeal]
|
||||
dealReq = request.(models.CreatingDealReq)
|
||||
}
|
||||
|
||||
var resultInfo model.ResultContent
|
||||
err := json.Unmarshal([]byte(result.Content), &resultInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contactRuleMap := result.FieldsRule.Contact.ContactRuleMap
|
||||
|
||||
contactFields, err = wc.addContactFields(ctx, contactFields, resultInfo.Name, model.TypeContactName, contactRuleMap)
|
||||
if err != nil {
|
||||
wc.logger.Error("error getting field data in addContactFields --- Name", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
if len(resultInfo.Phone) > 4 || resultInfo.Phone != "" {
|
||||
contactFields, err = wc.addContactFields(ctx, contactFields, resultInfo.Phone, model.TypeContactPhone, contactRuleMap)
|
||||
if err != nil {
|
||||
wc.logger.Error("error getting field data in addContactFields --- Phone", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
contactFields, err = wc.addContactFields(ctx, contactFields, resultInfo.Text, model.TypeContactText, contactRuleMap)
|
||||
if err != nil {
|
||||
wc.logger.Error("error getting field data in addContactFields --- Text", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
contactFields, err = wc.addContactFields(ctx, contactFields, resultInfo.Email, model.TypeContactEmail, contactRuleMap)
|
||||
if err != nil {
|
||||
wc.logger.Error("error getting field data in addContactFields --- Email", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
contactFields, err = wc.addContactFields(ctx, contactFields, resultInfo.Address, model.TypeContactAddress, contactRuleMap)
|
||||
if err != nil {
|
||||
wc.logger.Error("error getting field data in addContactFields --- Address", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//check duplicate contacts
|
||||
var fields []string
|
||||
var contactID int32
|
||||
|
||||
name := resultInfo.Name
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("empty name, quiz %d, triggered by answer - %d", result.QuizID, result.AnswerID)
|
||||
}
|
||||
|
||||
if len(resultInfo.Phone) > 4 || resultInfo.Phone != "" {
|
||||
fields = append(fields, resultInfo.Phone)
|
||||
}
|
||||
|
||||
if resultInfo.Email != "" {
|
||||
fields = append(fields, resultInfo.Email)
|
||||
}
|
||||
|
||||
existContactData, err := wc.bitrixRepo.BitrixRepo.GetExistingContactBitrix(ctx, result.AmoAccountID, fields)
|
||||
if err != nil && !errors.Is(err, pj_errors.ErrNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
if errors.Is(err, pj_errors.ErrNotFound) || len(existContactData) == 0 {
|
||||
fmt.Println("NO CONTACT", contactFields)
|
||||
contactReq := models.CreateContactReq{
|
||||
Fields: models.ContactFields{
|
||||
Name: name,
|
||||
TypeID: result.TypeID,
|
||||
SourceID: result.SourceID,
|
||||
Opened: "Y",
|
||||
UtmSource: result.UTMs["utm_source"],
|
||||
UtmCampaign: result.UTMs["utm_campaign"],
|
||||
UtmContent: result.UTMs["utm_content"],
|
||||
UtmMedium: result.UTMs["utm_medium"],
|
||||
UtmTerm: result.UTMs["utm_term"],
|
||||
},
|
||||
}
|
||||
|
||||
contactReqMap := models.FormattingToMap(&contactReq, contactFields)
|
||||
contactID, err = wc.bitrixClient.CreateContact(contactReqMap, result.AccessToken, result.SubDomain)
|
||||
if err != nil {
|
||||
wc.logger.Error("error creating contact", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(resultInfo.Phone) > 4 || resultInfo.Phone != "" {
|
||||
_, err = wc.bitrixRepo.BitrixRepo.InsertContactBitrix(ctx, model.ContactBitrix{
|
||||
AccountID: result.AmoAccountID,
|
||||
BitrixID: contactID,
|
||||
Field: resultInfo.Phone,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if resultInfo.Email != "" {
|
||||
_, err = wc.bitrixRepo.BitrixRepo.InsertContactBitrix(ctx, model.ContactBitrix{
|
||||
AccountID: result.AmoAccountID,
|
||||
BitrixID: contactID,
|
||||
Field: resultInfo.Email,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else if existContactData != nil && len(existContactData) > 0 {
|
||||
contactID, err = wc.chooseAndCreateContact(ctx, result, resultInfo, existContactData, contactFields, contactRuleMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
companyReq := models.CompanyReq{
|
||||
Fields: models.CompanyFields{
|
||||
Title: fmt.Sprintf("Компания %d", result.AnswerID),
|
||||
AssignedByID: performerIDInt,
|
||||
Opened: "Y",
|
||||
UtmSource: result.UTMs["utm_source"],
|
||||
UtmCampaign: result.UTMs["utm_campaign"],
|
||||
UtmContent: result.UTMs["utm_content"],
|
||||
UtmMedium: result.UTMs["utm_medium"],
|
||||
UtmTerm: result.UTMs["utm_term"],
|
||||
ContactID: contactID,
|
||||
},
|
||||
}
|
||||
|
||||
companyReqMap := models.FormattingToMap(&companyReq, companyFields)
|
||||
companyID, err := wc.bitrixClient.CreateCompany(companyReqMap, result.AccessToken, result.SubDomain)
|
||||
if err != nil {
|
||||
wc.logger.Error("error creating company", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var reqMap map[string]map[string]interface{}
|
||||
|
||||
if result.LeadFlag {
|
||||
fmt.Println("leadFields", leadFields)
|
||||
leadReq.Fields.CompanyID = companyID
|
||||
leadReq.Fields.ContactIDs = append(leadReq.Fields.ContactIDs, contactID)
|
||||
reqMap = models.FormattingToMap(&leadReq, leadFields)
|
||||
} else {
|
||||
fmt.Println("dealFields", dealFields)
|
||||
dealReq.Fields.CompanyID = companyID
|
||||
dealReq.Fields.ContactIDs = append(dealReq.Fields.ContactIDs, contactID)
|
||||
//dealFields["UF_CRM_1729778229491"] = "UF_CRM_1729778229491"
|
||||
reqMap = models.FormattingToMap(&dealReq, dealFields)
|
||||
}
|
||||
|
||||
return reqMap, nil
|
||||
}
|
||||
|
||||
func (wc *DealsWorker) addContactFields(ctx context.Context, contactFields map[string]string, fieldValue string, fieldType model.ContactQuizConfig, fieldMap map[string]string) (map[string]string, error) {
|
||||
if fieldValue != "" && fieldMap[string(fieldType)] != "" {
|
||||
fieldData, err := wc.bitrixRepo.BitrixRepo.GetFieldByID(ctx, fieldMap[string(fieldType)])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contactFields[fieldData.FieldName] = fieldValue
|
||||
}
|
||||
return contactFields, nil
|
||||
}
|
||||
|
||||
func (wc *DealsWorker) chooseAndCreateContact(ctx context.Context, result model.BitrixUsersTrueResults, resultInfo model.ResultContent, existingContacts map[int32][]model.ContactBitrix, contactFields map[string]string, contactRuleMap map[string]string) (int32, error) {
|
||||
var err error
|
||||
// 1 ищем контакт в котором совпадает и телефон и емайл
|
||||
if (len(resultInfo.Phone) > 4 || resultInfo.Phone != "") && resultInfo.Email != "" {
|
||||
phoneMatchedContacts := make(map[int32]bool)
|
||||
for _, contactVariants := range existingContacts {
|
||||
for _, contact := range contactVariants {
|
||||
if contact.Field == resultInfo.Phone {
|
||||
phoneMatchedContacts[contact.BitrixID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, contactVariants := range existingContacts {
|
||||
for _, contact := range contactVariants {
|
||||
if contact.Field == resultInfo.Email {
|
||||
if _, ok := phoneMatchedContacts[contact.BitrixID]; ok {
|
||||
wc.logger.Info("нашлось телефон и емайл в бд, с одинаковым битрикс ид", zap.Any("битрикс ид", contact.BitrixID), zap.String("Name", resultInfo.Name), zap.String("Phone", resultInfo.Phone), zap.String("Email", resultInfo.Email))
|
||||
return contact.BitrixID, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var phoneContactID, emailContactID int32
|
||||
var phoneID int64 /*emailID*/
|
||||
for _, contactVariants := range existingContacts {
|
||||
for _, contact := range contactVariants {
|
||||
if contact.Field == resultInfo.Phone {
|
||||
phoneContactID = contact.BitrixID
|
||||
phoneID = contact.ID
|
||||
}
|
||||
if contact.Field == resultInfo.Email {
|
||||
emailContactID = contact.BitrixID
|
||||
//emailID = contact.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if phoneContactID != 0 && emailContactID != 0 && phoneContactID != emailContactID {
|
||||
wc.logger.Info("нашлось телефон и емайл в бд, но это пока разные контакты", zap.String("Name", resultInfo.Name), zap.String("Phone", resultInfo.Phone), zap.String("Email", resultInfo.Email))
|
||||
// делаем обновление телефона там где уже есть email
|
||||
valuePhone := make(map[string]string)
|
||||
valuePhone, err = wc.addContactFields(ctx, valuePhone, resultInfo.Phone, model.TypeContactPhone, contactRuleMap)
|
||||
if err != nil {
|
||||
wc.logger.Error("error adding contact fields", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
name := resultInfo.Name
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("empty name, quiz %d, triggered by answer - %d", result.QuizID, result.AnswerID)
|
||||
}
|
||||
|
||||
contactUpdateReq := models.CreateContactReq{
|
||||
Fields: models.ContactFields{
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
contactUpdateReqMap := models.FormattingToMap(&contactUpdateReq, valuePhone)
|
||||
|
||||
err = wc.bitrixClient.UpdateContact(contactUpdateReqMap, result.AccessToken, result.SubDomain, emailContactID)
|
||||
if err != nil {
|
||||
wc.logger.Error("error updating contact", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = wc.bitrixRepo.BitrixRepo.UpdateBitrixContact(ctx, phoneID, resultInfo.Phone, emailContactID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// todo пока без линковки
|
||||
//_, err = wc.amoClient.LinkedContactToContact([]models.LinkedContactReq{
|
||||
// {
|
||||
// ToEntityID: emailContactID,
|
||||
// ToEntityType: "contacts",
|
||||
// //Metadata: struct {
|
||||
// // //CatalogID int `json:"catalog_id"`
|
||||
// // //Quantity int `json:"quantity"`
|
||||
// // IsMain bool `json:"is_main"`
|
||||
// // //UpdatedBy int `json:"updated_by"`
|
||||
// // //PriceID int `json:"price_id"`
|
||||
// //}(struct {
|
||||
// // //CatalogID int
|
||||
// // //Quantity int
|
||||
// // IsMain bool
|
||||
// // //UpdatedBy int
|
||||
// // //PriceID int
|
||||
// //}{IsMain: true}),
|
||||
// },
|
||||
//}, result.SubDomain, result.AccessToken, phoneContactID)
|
||||
//if err != nil {
|
||||
// return 0, err
|
||||
//}
|
||||
|
||||
return emailContactID, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 2 ищем контакт только по телефону
|
||||
if len(resultInfo.Phone) > 4 || resultInfo.Phone != "" {
|
||||
for _, contactVariants := range existingContacts {
|
||||
for _, contact := range contactVariants {
|
||||
if contact.Field == resultInfo.Phone {
|
||||
// нашли контакт по телефону
|
||||
emailExists := false
|
||||
for _, variant := range existingContacts[contact.BitrixID] {
|
||||
if variant.Field != contact.Field {
|
||||
if variant.Field != "" {
|
||||
emailExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !emailExists && resultInfo.Email != "" {
|
||||
// email пустой обновляем контакт добавляя email, если не пустой
|
||||
wc.logger.Info("нашлось телефон, емайл не пустой, а в бд пустой. обновляем контакт", zap.String("Name", resultInfo.Name), zap.String("Phone", resultInfo.Phone), zap.String("Email", resultInfo.Email))
|
||||
valueEmail := make(map[string]string)
|
||||
valueEmail, err = wc.addContactFields(ctx, valueEmail, resultInfo.Email, model.TypeContactEmail, contactRuleMap)
|
||||
if err != nil {
|
||||
wc.logger.Error("error adding contact fields", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
name := resultInfo.Name
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("empty name, quiz %d, triggered by answer - %d", result.QuizID, result.AnswerID)
|
||||
}
|
||||
|
||||
contactUpdateReq := models.CreateContactReq{
|
||||
Fields: models.ContactFields{
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
contactUpdateReqMap := models.FormattingToMap(&contactUpdateReq, valueEmail)
|
||||
|
||||
err = wc.bitrixClient.UpdateContact(contactUpdateReqMap, result.AccessToken, result.SubDomain, contact.BitrixID)
|
||||
if err != nil {
|
||||
wc.logger.Error("error updating contact", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
_, err = wc.bitrixRepo.BitrixRepo.InsertContactBitrix(ctx, model.ContactBitrix{
|
||||
AccountID: result.AmoAccountID,
|
||||
BitrixID: contact.BitrixID,
|
||||
Field: resultInfo.Email,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return contact.BitrixID, nil
|
||||
}
|
||||
if emailExists && resultInfo.Email != "" {
|
||||
// email не пустой значит это новый контакт создаем если наш email тоже не пустой
|
||||
wc.logger.Info("нашлось телефон, емайл не пустой и в бд не пустой. создаем новый контакт", zap.String("Name", resultInfo.Name), zap.String("Phone", resultInfo.Phone), zap.String("Email", resultInfo.Email))
|
||||
name := resultInfo.Name
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("empty name, quiz %d, triggered by answer - %d", result.QuizID, result.AnswerID)
|
||||
}
|
||||
|
||||
contactCreateReq := models.CreateContactReq{
|
||||
Fields: models.ContactFields{
|
||||
Name: name,
|
||||
TypeID: result.TypeID,
|
||||
SourceID: result.SourceID,
|
||||
Opened: "Y",
|
||||
UtmSource: result.UTMs["utm_source"],
|
||||
UtmCampaign: result.UTMs["utm_campaign"],
|
||||
UtmContent: result.UTMs["utm_content"],
|
||||
UtmMedium: result.UTMs["utm_medium"],
|
||||
UtmTerm: result.UTMs["utm_term"],
|
||||
},
|
||||
}
|
||||
|
||||
contactCreateReqMap := models.FormattingToMap(&contactCreateReq, contactFields)
|
||||
|
||||
contactID, err := wc.bitrixClient.CreateContact(contactCreateReqMap, result.AccessToken, result.SubDomain)
|
||||
if err != nil {
|
||||
wc.logger.Error("error creating contact", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = wc.bitrixRepo.BitrixRepo.InsertContactBitrix(ctx, model.ContactBitrix{
|
||||
AccountID: result.AmoAccountID,
|
||||
BitrixID: contactID,
|
||||
Field: resultInfo.Phone,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = wc.bitrixRepo.BitrixRepo.InsertContactBitrix(ctx, model.ContactBitrix{
|
||||
AccountID: result.AmoAccountID,
|
||||
BitrixID: contactID,
|
||||
Field: resultInfo.Email,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return contactID, nil
|
||||
}
|
||||
// если пустой то это нужный контакт возвращаем его id, так как если мейл пустой у нас но номер совпадает а в бд не пустой значит оно нам надо
|
||||
wc.logger.Info("нашлось телефон, емайл пустой возвращаем существующий контакт", zap.String("Name", resultInfo.Name), zap.String("Phone", resultInfo.Phone), zap.String("Email", resultInfo.Email))
|
||||
return contact.BitrixID, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3 ищем контакт только по email
|
||||
if resultInfo.Email != "" {
|
||||
for _, contactVariants := range existingContacts {
|
||||
for _, contact := range contactVariants {
|
||||
if contact.Field == resultInfo.Email {
|
||||
// нашли контакт по email
|
||||
phoneExists := false
|
||||
for _, variant := range existingContacts[contact.BitrixID] {
|
||||
if variant.Field != contact.Field {
|
||||
if variant.Field != "" {
|
||||
phoneExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !phoneExists && (len(resultInfo.Phone) > 4 || resultInfo.Phone != "") {
|
||||
// телефон пустой обновляем контакт добавляя телефон, если не пустой
|
||||
wc.logger.Info("нашлось емайл, телефон не пустой, а в бд пустой. обновляем контакт", zap.String("Name", resultInfo.Name), zap.String("Phone", resultInfo.Phone), zap.String("Email", resultInfo.Email))
|
||||
valuePhone := make(map[string]string)
|
||||
valuePhone, err = wc.addContactFields(ctx, valuePhone, resultInfo.Phone, model.TypeContactPhone, contactRuleMap)
|
||||
if err != nil {
|
||||
wc.logger.Error("error adding contact fields", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
name := resultInfo.Name
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("empty name, quiz %d, triggered by answer - %d", result.QuizID, result.AnswerID)
|
||||
}
|
||||
|
||||
contactUpdateReq := models.CreateContactReq{
|
||||
Fields: models.ContactFields{
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
contactUpdateReqMap := models.FormattingToMap(&contactUpdateReq, valuePhone)
|
||||
|
||||
err := wc.bitrixClient.UpdateContact(contactUpdateReqMap, result.AccessToken, result.SubDomain, contact.BitrixID)
|
||||
if err != nil {
|
||||
wc.logger.Error("error updating contact", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = wc.bitrixRepo.BitrixRepo.InsertContactBitrix(ctx, model.ContactBitrix{
|
||||
AccountID: result.AmoAccountID,
|
||||
BitrixID: contact.BitrixID,
|
||||
Field: resultInfo.Phone,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return contact.BitrixID, nil
|
||||
}
|
||||
if phoneExists && (len(resultInfo.Phone) > 4 || resultInfo.Phone != "") {
|
||||
// телефон не пустой значит это новый контакт создаем если наш телефон не пустой
|
||||
wc.logger.Info("нашлось емайл, телефон не пустой и в бд не пустой. создаем новый контакт", zap.String("Name", resultInfo.Name), zap.String("Phone", resultInfo.Phone), zap.String("Email", resultInfo.Email))
|
||||
name := resultInfo.Name
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("empty name, quiz %d, triggered by answer - %d", result.QuizID, result.AnswerID)
|
||||
}
|
||||
|
||||
contactCreateReq := models.CreateContactReq{
|
||||
Fields: models.ContactFields{
|
||||
Name: name,
|
||||
TypeID: result.TypeID,
|
||||
SourceID: result.SourceID,
|
||||
Opened: "Y",
|
||||
UtmSource: result.UTMs["utm_source"],
|
||||
UtmCampaign: result.UTMs["utm_campaign"],
|
||||
UtmContent: result.UTMs["utm_content"],
|
||||
UtmMedium: result.UTMs["utm_medium"],
|
||||
UtmTerm: result.UTMs["utm_term"],
|
||||
},
|
||||
}
|
||||
|
||||
contactCreateMapReq := models.FormattingToMap(&contactCreateReq, contactFields)
|
||||
|
||||
contactID, err := wc.bitrixClient.CreateContact(contactCreateMapReq, result.AccessToken, result.SubDomain)
|
||||
if err != nil {
|
||||
wc.logger.Error("error creating contact", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = wc.bitrixRepo.BitrixRepo.InsertContactBitrix(ctx, model.ContactBitrix{
|
||||
AccountID: result.AmoAccountID,
|
||||
BitrixID: contactID,
|
||||
Field: resultInfo.Phone,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = wc.bitrixRepo.BitrixRepo.InsertContactBitrix(ctx, model.ContactBitrix{
|
||||
AccountID: result.AmoAccountID,
|
||||
BitrixID: contactID,
|
||||
Field: resultInfo.Email,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return contactID, nil
|
||||
}
|
||||
|
||||
// если пустой то это нужный контакт возвращаем его id, так как если телефон пустой у нас но мейл совпадает а в бд не пустой значит оно нам надо
|
||||
wc.logger.Info("нашлось емайл, телефон пустой возвращаем существующий контакт", zap.String("Name", resultInfo.Name), zap.String("Phone", resultInfo.Phone), zap.String("Email", resultInfo.Email))
|
||||
return contact.BitrixID, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wc.logger.Info("ничего не нашлось, создаем новый контакт", zap.String("Name", resultInfo.Name), zap.String("Phone", resultInfo.Phone), zap.String("Email", resultInfo.Email))
|
||||
// если дошлю до сюда то это новый контакт с новым email and phone
|
||||
name := resultInfo.Name
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("empty name, quiz %d, triggered by answer - %d", result.QuizID, result.AnswerID)
|
||||
}
|
||||
|
||||
contactCreateReq := models.CreateContactReq{
|
||||
Fields: models.ContactFields{
|
||||
Name: name,
|
||||
TypeID: result.TypeID,
|
||||
SourceID: result.SourceID,
|
||||
Opened: "Y",
|
||||
UtmSource: result.UTMs["utm_source"],
|
||||
UtmCampaign: result.UTMs["utm_campaign"],
|
||||
UtmContent: result.UTMs["utm_content"],
|
||||
UtmMedium: result.UTMs["utm_medium"],
|
||||
UtmTerm: result.UTMs["utm_term"],
|
||||
},
|
||||
}
|
||||
|
||||
contactCreateMapReq := models.FormattingToMap(&contactCreateReq, contactFields)
|
||||
|
||||
contactID, err := wc.bitrixClient.CreateContact(contactCreateMapReq, result.AccessToken, result.SubDomain)
|
||||
if err != nil {
|
||||
wc.logger.Error("error creating contact", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(resultInfo.Phone) > 4 || resultInfo.Phone != "" {
|
||||
_, err = wc.bitrixRepo.BitrixRepo.InsertContactBitrix(ctx, model.ContactBitrix{
|
||||
AccountID: result.AmoAccountID,
|
||||
BitrixID: contactID,
|
||||
Field: resultInfo.Phone,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if resultInfo.Email != "" {
|
||||
_, err = wc.bitrixRepo.BitrixRepo.InsertContactBitrix(ctx, model.ContactBitrix{
|
||||
AccountID: result.AmoAccountID,
|
||||
BitrixID: contactID,
|
||||
Field: resultInfo.Email,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return contactID, nil
|
||||
}
|
||||
|
||||
func (wc *DealsWorker) Stop(_ context.Context) error {
|
||||
return nil
|
||||
}
|
260
internal/workers/queueUpdater/queue_updater.go
Normal file
260
internal/workers/queueUpdater/queue_updater.go
Normal file
@ -0,0 +1,260 @@
|
||||
package queueUpdater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/twmb/franz-go/pkg/kgo"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/workers_methods"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
type QueueUpdater struct {
|
||||
logger *zap.Logger
|
||||
kafkaClient *kgo.Client
|
||||
methods *workers_methods.Methods
|
||||
}
|
||||
|
||||
type Deps struct {
|
||||
Logger *zap.Logger
|
||||
KafkaClient *kgo.Client
|
||||
Methods *workers_methods.Methods
|
||||
}
|
||||
|
||||
func NewQueueUpdater(deps Deps) *QueueUpdater {
|
||||
return &QueueUpdater{
|
||||
logger: deps.Logger,
|
||||
kafkaClient: deps.KafkaClient,
|
||||
methods: deps.Methods,
|
||||
}
|
||||
}
|
||||
|
||||
func (wc *QueueUpdater) Start(ctx context.Context) {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
wc.consumeMessages(ctx)
|
||||
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wc *QueueUpdater) consumeMessages(ctx context.Context) {
|
||||
fetches := wc.kafkaClient.PollFetches(ctx)
|
||||
iter := fetches.RecordIter()
|
||||
for !iter.Done() {
|
||||
record := iter.Next()
|
||||
var message models.KafkaMessage
|
||||
|
||||
err := json.Unmarshal(record.Value, &message)
|
||||
if err != nil {
|
||||
wc.logger.Error("error unmarshal kafka message:", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
err = wc.processMessages(ctx, message)
|
||||
if err != nil {
|
||||
wc.logger.Error("error processing kafka message:", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wc *QueueUpdater) processMessages(ctx context.Context, message models.KafkaMessage) error {
|
||||
switch message.Type {
|
||||
case models.UsersUpdate:
|
||||
token, err := wc.methods.GetTokenByID(ctx, message.AccountID)
|
||||
if err != nil {
|
||||
wc.logger.Error("error getting user token from db", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if token != nil {
|
||||
err = wc.methods.CheckUsers(ctx, []model.Token{*token})
|
||||
if err != nil {
|
||||
wc.logger.Error("error update user information in queue worker", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case models.PipelinesUpdate:
|
||||
token, err := wc.methods.GetTokenByID(ctx, message.AccountID)
|
||||
if err != nil {
|
||||
wc.logger.Error("error getting user token from db", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if token != nil {
|
||||
err = wc.methods.CheckPipelines(ctx, []model.Token{*token})
|
||||
if err != nil {
|
||||
wc.logger.Error("error update user pipelines and steps information in queue worker", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
case models.StepsUpdate:
|
||||
token, err := wc.methods.GetTokenByID(ctx, message.AccountID)
|
||||
if err != nil {
|
||||
wc.logger.Error("error getting user token from db", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if token != nil {
|
||||
err = wc.methods.CheckSteps(ctx, []model.Token{*token})
|
||||
if err != nil {
|
||||
wc.logger.Error("error update user pipelines and steps information in queue worker", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case models.FieldsUpdate:
|
||||
token, err := wc.methods.GetTokenByID(ctx, message.AccountID)
|
||||
if err != nil {
|
||||
wc.logger.Error("error getting user token from db", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if token != nil {
|
||||
err = wc.methods.CheckFields(ctx, []model.Token{*token})
|
||||
if err != nil {
|
||||
wc.logger.Error("error update user fields information in queue worker", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//case models.TagsUpdate:
|
||||
// token, err := wc.methods.GetTokenByID(ctx, message.AccountID)
|
||||
// if err != nil {
|
||||
// wc.logger.Error("error getting user token from db", zap.Error(err))
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// if token != nil {
|
||||
// err = wc.methods.CheckTags(ctx, []model.Token{*token})
|
||||
// if err != nil {
|
||||
// wc.logger.Error("error update user tags information in queue worker", zap.Error(err))
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
case models.UserCreate:
|
||||
token, err := wc.methods.CreateUserFromWebHook(ctx, message)
|
||||
if err != nil {
|
||||
wc.logger.Error("error creating user from webhook request", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
err = wc.methods.CheckUsers(ctx, []model.Token{token})
|
||||
if err != nil {
|
||||
wc.logger.Error("error update user information in queue worker", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
err = wc.methods.CheckPipelines(ctx, []model.Token{token})
|
||||
if err != nil {
|
||||
wc.logger.Error("error update user pipelines information in queue worker", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
err = wc.methods.CheckSteps(ctx, []model.Token{token})
|
||||
if err != nil {
|
||||
wc.logger.Error("error update user steps information in queue worker", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
err = wc.methods.CheckFields(ctx, []model.Token{token})
|
||||
if err != nil {
|
||||
wc.logger.Error("error update user fields information in queue worker", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
//err = wc.methods.CheckTags(ctx, []model.Token{*token})
|
||||
//if err != nil {
|
||||
// wc.logger.Error("error update user tags information in queue worker", zap.Error(err))
|
||||
// return err
|
||||
//}
|
||||
case models.AllDataUpdate:
|
||||
// сначала получаем список токенов
|
||||
newTokens, err := wc.methods.UpdateTokens(ctx)
|
||||
if err != nil {
|
||||
wc.logger.Error("error updating tokens and getting new tokens", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if len(newTokens) > 0 {
|
||||
// обновляем информацию о пользователях
|
||||
err = wc.methods.CheckUsers(ctx, newTokens)
|
||||
if err != nil {
|
||||
wc.logger.Error("error update users information", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// обновляем информацию о pipelines
|
||||
err = wc.methods.CheckPipelines(ctx, newTokens)
|
||||
if err != nil {
|
||||
wc.logger.Error("error updating users pipelines and users pipelines-steps", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// обновляем информацию о steps
|
||||
err = wc.methods.CheckSteps(ctx, newTokens)
|
||||
if err != nil {
|
||||
wc.logger.Error("error updating users pipelines and users pipelines-steps", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// обновляем информацию о tags
|
||||
//err = wc.methods.CheckTags(ctx, newTokens)
|
||||
//if err != nil {
|
||||
// wc.logger.Error("error updating users tags", zap.Error(err))
|
||||
// return err
|
||||
//}
|
||||
|
||||
// обновляем информацию о fields
|
||||
err = wc.methods.CheckFields(ctx, newTokens)
|
||||
if err != nil {
|
||||
wc.logger.Error("error updating users fields", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
case models.RuleCheck:
|
||||
token, err := wc.methods.GetTokenByID(ctx, message.AccountID)
|
||||
if err != nil {
|
||||
wc.logger.Error("error getting user token from db", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
if token != nil {
|
||||
err = wc.methods.CheckFields(ctx, []model.Token{*token})
|
||||
if err != nil {
|
||||
wc.logger.Error("error update user fields information in queue worker", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
err = wc.methods.CheckFieldRule(ctx, token.AccessToken, message)
|
||||
if err != nil {
|
||||
wc.logger.Error("error check field rules for fields rules", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
case models.UserReLogin:
|
||||
err := wc.methods.UserReLogin(ctx, message)
|
||||
if err != nil {
|
||||
wc.logger.Error("error update user information in re-login method", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
wc.logger.Error("incorrect message type", zap.Any("Type:", message))
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wc *QueueUpdater) Stop(_ context.Context) error {
|
||||
return nil
|
||||
}
|
779
internal/workers_methods/methods.go
Normal file
779
internal/workers_methods/methods.go
Normal file
@ -0,0 +1,779 @@
|
||||
package workers_methods
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/tools"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/pkg/bitrixClient"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Methods struct {
|
||||
repo *dal.BitrixDal
|
||||
bitrixClient *bitrixClient.Bitrix
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
type Deps struct {
|
||||
Repo *dal.BitrixDal
|
||||
BitrixClient *bitrixClient.Bitrix
|
||||
Logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewWorkersMethods(deps Deps) *Methods {
|
||||
return &Methods{
|
||||
repo: deps.Repo,
|
||||
bitrixClient: deps.BitrixClient,
|
||||
logger: deps.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Methods) UpdateTokens(ctx context.Context) ([]model.Token, error) {
|
||||
allTokens, err := m.repo.BitrixRepo.GetAllTokens(ctx)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting all tokens from db in UpdateTokens", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, oldToken := range allTokens {
|
||||
req := models.UpdateWebHookReq{
|
||||
GrantType: "refresh_token",
|
||||
RefreshToken: oldToken.RefreshToken,
|
||||
}
|
||||
|
||||
resp, err := m.bitrixClient.CreateWebHook(&req, false)
|
||||
if err != nil {
|
||||
m.logger.Error("error create webhook in UpdateTokens", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
newToken := model.Token{
|
||||
AccountID: oldToken.AccountID,
|
||||
RefreshToken: resp.RefreshToken,
|
||||
AccessToken: resp.AccessToken,
|
||||
Expiration: time.Now().Unix() + resp.ExpiresIn,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}
|
||||
|
||||
err = m.repo.BitrixRepo.WebhookUpdate(ctx, newToken)
|
||||
if err != nil {
|
||||
m.logger.Error("error update token in db", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
newTokens, err := m.repo.BitrixRepo.GetAllTokens(ctx)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting all new updated tokens from db in UpdateTokens", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newTokens, nil
|
||||
}
|
||||
|
||||
func (m *Methods) CheckUsers(ctx context.Context, allTokens []model.Token) error {
|
||||
listUser := make(map[string][]models.User)
|
||||
for _, token := range allTokens {
|
||||
currentCompany, err := m.repo.BitrixRepo.GetCurrentAccount(ctx, token.AccountID)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting current company", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
userData, err := m.bitrixClient.GetUserList(token.AccessToken, currentCompany.Subdomain)
|
||||
if err != nil {
|
||||
m.logger.Error("error fetching list users", zap.Error(err))
|
||||
break
|
||||
}
|
||||
|
||||
listUser[token.AccountID] = append(listUser[token.AccountID], userData.Result...)
|
||||
}
|
||||
|
||||
for accountID, users := range listUser {
|
||||
currentCompany, err := m.repo.BitrixRepo.GetCurrentAccount(ctx, accountID)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting current company", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
currentUserUsers, err := m.repo.BitrixRepo.GetUserUsersByID(ctx, currentCompany.BitrixID)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting user users by bitrix user id", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
found := false
|
||||
for _, currentUser := range currentUserUsers {
|
||||
if user.ID == currentUser.BitrixIDUserID {
|
||||
found = true
|
||||
err := m.repo.BitrixRepo.UpdateBitrixAccountUser(ctx, model.BitrixAccountUser{
|
||||
AccountID: currentUser.AccountID,
|
||||
BitrixIDUserID: currentUser.BitrixIDUserID,
|
||||
Name: user.Name,
|
||||
LastName: user.LastName,
|
||||
SecondName: user.SecondName,
|
||||
Title: user.Title,
|
||||
Email: user.Email,
|
||||
UFDepartment: user.UFDepartment,
|
||||
WorkPosition: user.WorkPosition,
|
||||
})
|
||||
if err != nil {
|
||||
m.logger.Error("failed update user bitrix account in db", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
err := m.repo.BitrixRepo.AddBitrixAccountUser(ctx, model.BitrixAccountUser{
|
||||
AccountID: currentCompany.BitrixID,
|
||||
BitrixIDUserID: user.ID,
|
||||
Name: user.Name,
|
||||
LastName: user.LastName,
|
||||
SecondName: user.SecondName,
|
||||
Title: user.Title,
|
||||
Email: user.Email,
|
||||
UFDepartment: user.UFDepartment,
|
||||
WorkPosition: user.WorkPosition,
|
||||
})
|
||||
if err != nil {
|
||||
m.logger.Error("failed insert user bitrix account in db", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var deletedUserIDs []int64
|
||||
for _, currentUserUser := range currentUserUsers {
|
||||
found := false
|
||||
for _, user := range users {
|
||||
if currentUserUser.BitrixIDUserID == user.ID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
deletedUserIDs = append(deletedUserIDs, currentUserUser.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(deletedUserIDs) > 0 {
|
||||
err := m.repo.BitrixRepo.DeleteUsers(ctx, deletedUserIDs)
|
||||
if err != nil {
|
||||
m.logger.Error("error deleting users in db", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Methods) CheckPipelines(ctx context.Context, tokens []model.Token) error {
|
||||
for _, token := range tokens {
|
||||
currentCompany, err := m.repo.BitrixRepo.GetCurrentAccount(ctx, token.AccountID)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting bitrix company by account quiz id", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
currentCompanyPipelines, err := m.repo.BitrixRepo.GetUserPipelinesByID(ctx, currentCompany.BitrixID)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting company pipelines by bitrix id", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
var listPipelines []models.Category
|
||||
for _, categoryType := range model.CategoryArr {
|
||||
pipelinesResp, err := m.bitrixClient.GetListPipelines(categoryType, token.AccessToken, currentCompany.Subdomain)
|
||||
if err != nil {
|
||||
m.logger.Error("error fetching list pipelines from bitrix", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
listPipelines = append(listPipelines, pipelinesResp.Result.Categories...)
|
||||
}
|
||||
|
||||
if len(listPipelines) > 0 {
|
||||
receivedPipelines := tools.ToPipeline(listPipelines, currentCompany.BitrixID)
|
||||
err = m.repo.BitrixRepo.CheckPipelines(ctx, receivedPipelines)
|
||||
if err != nil {
|
||||
m.logger.Error("error checking pipelines", zap.Error(err))
|
||||
}
|
||||
|
||||
var deletedPipelineIDs []int64
|
||||
for _, currentUserPipeline := range currentCompanyPipelines {
|
||||
found := false
|
||||
for _, receivedPipeline := range receivedPipelines {
|
||||
if currentUserPipeline.BitrixID == receivedPipeline.BitrixID && currentUserPipeline.AccountID == currentCompany.BitrixID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
deletedPipelineIDs = append(deletedPipelineIDs, currentUserPipeline.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(deletedPipelineIDs) > 0 {
|
||||
err := m.repo.BitrixRepo.DeletePipelines(ctx, deletedPipelineIDs)
|
||||
if err != nil {
|
||||
m.logger.Error("error deleting pipelines in db", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Methods) CheckSteps(ctx context.Context, tokens []model.Token) error {
|
||||
for _, token := range tokens {
|
||||
currentCompany, err := m.repo.BitrixRepo.GetCurrentAccount(ctx, token.AccountID)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting bitrix company by account quiz id", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
currentCompanySteps, err := m.repo.BitrixRepo.GetUserStepsByID(ctx, currentCompany.BitrixID)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting company steps by bitrix id", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
var listSteps []models.Steps
|
||||
stepsResp, err := m.bitrixClient.GetListSteps(token.AccessToken, currentCompany.Subdomain)
|
||||
if err != nil {
|
||||
m.logger.Error("error fetching list steps from bitrix", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
listSteps = append(listSteps, stepsResp.Result...)
|
||||
|
||||
if len(listSteps) > 0 {
|
||||
receivedSteps, err := tools.ToStep(listSteps, currentCompany.BitrixID)
|
||||
if err != nil {
|
||||
m.logger.Error("error converting steps to bitrix", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
err = m.repo.BitrixRepo.CheckSteps(ctx, receivedSteps)
|
||||
if err != nil {
|
||||
m.logger.Error("error checking steps", zap.Error(err))
|
||||
}
|
||||
|
||||
var deletedStepIDs []int64
|
||||
for _, currentUserStep := range currentCompanySteps {
|
||||
found := false
|
||||
for _, receivedStep := range receivedSteps {
|
||||
if currentUserStep.BitrixID == receivedStep.BitrixID && currentUserStep.AccountID == currentCompany.BitrixID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
deletedStepIDs = append(deletedStepIDs, currentUserStep.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(deletedStepIDs) > 0 {
|
||||
err := m.repo.BitrixRepo.DeleteSteps(ctx, deletedStepIDs)
|
||||
if err != nil {
|
||||
m.logger.Error("error deleting steps in db", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//func (m *Methods) CheckTags(ctx context.Context, tokens []model.Token) error {
|
||||
// for _, token := range tokens {
|
||||
// user, err := m.repo.AmoRepo.GetCurrentAccount(ctx, token.AccountID)
|
||||
// if err != nil {
|
||||
// m.logger.Error("error getting amoUserInfo by account quiz id", zap.Error(err))
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// currentUserTags, err := m.repo.AmoRepo.GetUserTagsByID(ctx, user.AmoID)
|
||||
// if err != nil {
|
||||
// m.logger.Error("error getting user tags by amo id", zap.Error(err))
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// var wg sync.WaitGroup
|
||||
// wg.Add(4)
|
||||
//
|
||||
// var tagsMap sync.Map
|
||||
// entityTypes := []model.EntityType{model.LeadsType, model.ContactsType, model.CompaniesType, model.CustomersType}
|
||||
// for _, entityType := range entityTypes {
|
||||
// go func(entityType model.EntityType) {
|
||||
// defer wg.Done()
|
||||
// page := 1
|
||||
// limit := 250
|
||||
//
|
||||
// for {
|
||||
// req := models.GetListTagsReq{
|
||||
// Page: page,
|
||||
// Limit: limit,
|
||||
// EntityType: entityType,
|
||||
// }
|
||||
// tags, err := m.amoClient.GetListTags(req, token.AccessToken, user.Subdomain)
|
||||
// if err != nil {
|
||||
// m.logger.Error("error getting list of tags", zap.Error(err))
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// if tags == nil || len(tags.Embedded.Tags) == 0 {
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// tagsMap.Store(entityType, tags.Embedded.Tags)
|
||||
//
|
||||
// page++
|
||||
// }
|
||||
// }(entityType)
|
||||
// }
|
||||
//
|
||||
// wg.Wait()
|
||||
//
|
||||
// var deletedTagIDs []int64
|
||||
// for _, currentUserTag := range currentUserTags {
|
||||
// found := false
|
||||
// for _, entityType := range entityTypes {
|
||||
// if tags, ok := tagsMap.Load(entityType); ok {
|
||||
// if len(tags.([]models.Tag)) > 0 {
|
||||
// receivedTags := tools.ToTag(tags.([]models.Tag), entityType)
|
||||
// for _, tag := range receivedTags {
|
||||
// if currentUserTag.Amoid == tag.Amoid && currentUserTag.Accountid == user.AmoID && currentUserTag.Entity == entityType {
|
||||
// found = true
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if found {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if !found {
|
||||
// deletedTagIDs = append(deletedTagIDs, currentUserTag.ID)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if len(deletedTagIDs) > 0 {
|
||||
// err = m.repo.AmoRepo.DeleteTags(ctx, deletedTagIDs)
|
||||
// if err != nil {
|
||||
// m.logger.Error("error deleting tags in db", zap.Error(err))
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// for _, entityType := range entityTypes {
|
||||
// if tags, ok := tagsMap.Load(entityType); ok {
|
||||
// if len(tags.([]models.Tag)) > 0 {
|
||||
// err := m.repo.AmoRepo.CheckTags(ctx, tools.ToTag(tags.([]models.Tag), entityType), token.AccountID)
|
||||
// if err != nil {
|
||||
// switch entityType {
|
||||
// case model.LeadsType:
|
||||
// m.logger.Error("error updating leads tags in db", zap.Error(err))
|
||||
// return err
|
||||
// case model.ContactsType:
|
||||
// m.logger.Error("error updating contacts tags in db", zap.Error(err))
|
||||
// return err
|
||||
// case model.CompaniesType:
|
||||
// m.logger.Error("error updating companies tags in db", zap.Error(err))
|
||||
// return err
|
||||
// case model.CustomersType:
|
||||
// m.logger.Error("error updating customer tags in db", zap.Error(err))
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
//}
|
||||
|
||||
func (m *Methods) CheckFields(ctx context.Context, tokens []model.Token) error {
|
||||
for _, token := range tokens {
|
||||
currentCompany, err := m.repo.BitrixRepo.GetCurrentAccount(ctx, token.AccountID)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting company by account quiz id", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
currentUserFields, err := m.repo.BitrixRepo.GetUserFieldsByID(ctx, currentCompany.BitrixID)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting user fields by bitrix id", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(4)
|
||||
|
||||
var fieldsMap sync.Map
|
||||
entityTypes := []model.FieldsType{model.FieldTypeCompany, model.FieldTypeLead, model.FieldTypeContact, model.FieldTypeDeal}
|
||||
for _, entityType := range entityTypes {
|
||||
go func(entityType model.FieldsType) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
fields, err := m.bitrixClient.GetListFields(entityType, token.AccessToken, currentCompany.Subdomain)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting list of fields", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
if fields == nil || len(fields.Result) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
fieldsMap.Store(entityType, fields.Result)
|
||||
break
|
||||
}
|
||||
}(entityType)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
var deletedFieldIDs []int64
|
||||
for _, currentUserField := range currentUserFields {
|
||||
found := false
|
||||
for _, entityType := range entityTypes {
|
||||
if fields, ok := fieldsMap.Load(entityType); ok {
|
||||
if len(fields.([]models.Fields)) > 0 {
|
||||
receivedFields := tools.ToField(fields.([]models.Fields), currentCompany.BitrixID)
|
||||
for _, field := range receivedFields {
|
||||
if currentUserField.BitrixID == field.BitrixID && currentUserField.AccountID == currentCompany.BitrixID && currentUserField.EntityID == entityType {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if found {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
deletedFieldIDs = append(deletedFieldIDs, currentUserField.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(deletedFieldIDs) > 0 {
|
||||
err = m.repo.BitrixRepo.DeleteFields(ctx, deletedFieldIDs)
|
||||
if err != nil {
|
||||
m.logger.Error("error deleting fields in db", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, entityType := range entityTypes {
|
||||
if fields, ok := fieldsMap.Load(entityType); ok {
|
||||
if len(fields.([]models.Fields)) > 0 {
|
||||
err := m.repo.BitrixRepo.CheckFields(ctx, tools.ToField(fields.([]models.Fields), currentCompany.BitrixID), token.AccountID)
|
||||
if err != nil {
|
||||
switch entityType {
|
||||
case model.FieldTypeLead:
|
||||
m.logger.Error("error updating leads fields in db", zap.Error(err))
|
||||
return err
|
||||
case model.FieldTypeContact:
|
||||
m.logger.Error("error updating contacts fields in db", zap.Error(err))
|
||||
return err
|
||||
case model.FieldTypeCompany:
|
||||
m.logger.Error("error updating companies fields in db", zap.Error(err))
|
||||
return err
|
||||
case model.FieldTypeDeal:
|
||||
m.logger.Error("error updating deal fields in db", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Methods) GetTokenByID(ctx context.Context, accountID string) (*model.Token, error) {
|
||||
token, err := m.repo.BitrixRepo.GetTokenByID(ctx, accountID)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting token by account id from db", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (m *Methods) CreateUserFromWebHook(ctx context.Context, msg models.KafkaMessage) (model.Token, error) {
|
||||
// получаем аксес и рефреш токены по коду авторизации, id битрикса ==member id
|
||||
forGetTokens := models.CreateWebHookReq{
|
||||
GrantType: "authorization_code",
|
||||
Code: msg.AuthCode,
|
||||
}
|
||||
|
||||
tokens, err := m.bitrixClient.CreateWebHook(&forGetTokens, true)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting webhook in CreateUserFromWebHook:", zap.Error(err))
|
||||
return model.Token{}, err
|
||||
}
|
||||
|
||||
fmt.Println("tokens", tokens)
|
||||
|
||||
if tokens.AccessToken == "" || tokens.RefreshToken == "" {
|
||||
return model.Token{}, errors.New("invalid token")
|
||||
}
|
||||
|
||||
toCreate := model.BitrixAccount{
|
||||
AccountID: msg.AccountID,
|
||||
BitrixID: msg.MemberID,
|
||||
Subdomain: msg.RefererURL,
|
||||
}
|
||||
|
||||
err = m.repo.BitrixRepo.CreateAccount(ctx, toCreate)
|
||||
if err != nil {
|
||||
m.logger.Error("error create account in db in CreateUserFromWebHook", zap.Error(err))
|
||||
return model.Token{}, err
|
||||
}
|
||||
|
||||
err = m.repo.BitrixRepo.WebhookCreate(ctx, model.Token{
|
||||
RefreshToken: tokens.RefreshToken,
|
||||
AccessToken: tokens.AccessToken,
|
||||
AccountID: msg.AccountID,
|
||||
AuthCode: msg.AuthCode,
|
||||
Expiration: time.Now().Unix() + tokens.ExpiresIn,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
})
|
||||
if err != nil {
|
||||
m.logger.Error("error adding tokens to db in CreateUserFromWebHook", zap.Error(err))
|
||||
return model.Token{}, err
|
||||
}
|
||||
|
||||
return model.Token{
|
||||
AccountID: msg.AccountID,
|
||||
RefreshToken: tokens.RefreshToken,
|
||||
AccessToken: tokens.AccessToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *Methods) CheckFieldRule(ctx context.Context, token string, msg models.KafkaMessage) error {
|
||||
var (
|
||||
leadIDs, companyIDs, dealIDs, contactIDs []int32
|
||||
leadQuestions, companyQuestions, dealQuestions, contactQuestions []model.Question
|
||||
questionsTypeMap = make(map[model.FieldsType][]model.Question)
|
||||
newFields []model.BitrixField
|
||||
lead, company, deal, contact model.BitrixFieldRule
|
||||
currentFieldsRule = msg.Rule.FieldsRule
|
||||
err error
|
||||
)
|
||||
|
||||
user, err := m.repo.BitrixRepo.GetCurrentAccount(ctx, msg.AccountID)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting user data by account id in check utms wc method", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
currentFields, err := m.repo.BitrixRepo.GetUserFieldsByID(ctx, user.BitrixID)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting user fields by amo account id", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
quiz, err := m.repo.QuizRepo.GetQuizById(ctx, msg.AccountID, uint64(msg.Rule.QuizID))
|
||||
if err != nil {
|
||||
m.logger.Error("error getting quiz by quizID and accountID", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
var quizConfig model.QuizContact
|
||||
err = json.Unmarshal([]byte(quiz.Config), &quizConfig)
|
||||
if err != nil {
|
||||
m.logger.Error("error serialization quizConfig to model QuizContact", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
leadIDs = tools.ToQuestionIDs(msg.Rule.FieldsRule.Lead.QuestionID)
|
||||
dealIDs = tools.ToQuestionIDs(msg.Rule.FieldsRule.Deal.QuestionID)
|
||||
companyIDs = tools.ToQuestionIDs(msg.Rule.FieldsRule.Company.QuestionID)
|
||||
contactIDs = tools.ToQuestionIDs(msg.Rule.FieldsRule.Contact.QuestionID)
|
||||
|
||||
getQuestions := func(questionIDs []int32, questions *[]model.Question) {
|
||||
if len(questionIDs) > 0 {
|
||||
*questions, err = m.repo.QuestionRepo.GetQuestionListByIDs(ctx, questionIDs)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting questions", zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getQuestions(leadIDs, &leadQuestions)
|
||||
getQuestions(dealIDs, &dealQuestions)
|
||||
getQuestions(companyIDs, &companyQuestions)
|
||||
getQuestions(contactIDs, &contactQuestions)
|
||||
|
||||
questionsTypeMap[model.FieldTypeLead] = append(questionsTypeMap[model.FieldTypeLead], leadQuestions...)
|
||||
questionsTypeMap[model.FieldTypeDeal] = append(questionsTypeMap[model.FieldTypeDeal], dealQuestions...)
|
||||
questionsTypeMap[model.FieldTypeCompany] = append(questionsTypeMap[model.FieldTypeCompany], companyQuestions...)
|
||||
questionsTypeMap[model.FieldTypeContact] = append(questionsTypeMap[model.FieldTypeContact], contactQuestions...)
|
||||
|
||||
toCreated, toUpdate := tools.ToCreatedUpdateQuestionRules(questionsTypeMap, currentFields)
|
||||
contactFieldsToCreate, forAdding := tools.ForContactRules(quizConfig, currentFields)
|
||||
|
||||
for entity, fields := range toCreated {
|
||||
if len(fields) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, field := range fields {
|
||||
field.GenFieldName()
|
||||
createdID, err := m.bitrixClient.AddFields(field, entity, token, user.Subdomain)
|
||||
if err != nil {
|
||||
m.logger.Error("error adding fields to amo", zap.Any("type", entity), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
// todo need checking in prod
|
||||
newFields = append(newFields, model.BitrixField{
|
||||
BitrixID: fmt.Sprintf("%d", createdID),
|
||||
EntityID: entity,
|
||||
FieldName: "UF_CRM_" + field.FieldName,
|
||||
EditFromLabel: field.EditFormLabel,
|
||||
FieldType: field.UserTypeID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(contactFieldsToCreate) > 0 {
|
||||
for _, contactField := range contactFieldsToCreate {
|
||||
contactField.GenFieldName()
|
||||
createdID, err := m.bitrixClient.AddFields(contactField, model.FieldTypeContact, token, user.Subdomain)
|
||||
if err != nil {
|
||||
m.logger.Error("error adding fields to amo", zap.Any("type", model.FieldTypeContact), zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
// todo need checking in prod
|
||||
newFields = append(newFields, model.BitrixField{
|
||||
BitrixID: fmt.Sprintf("%d", createdID),
|
||||
EntityID: model.FieldTypeContact,
|
||||
FieldName: "UF_CRM_" + contactField.FieldName,
|
||||
EditFromLabel: contactField.EditFormLabel,
|
||||
FieldType: contactField.UserTypeID,
|
||||
})
|
||||
|
||||
if _, ok := forAdding[contactField.EditFormLabel]; ok {
|
||||
forAdding[contactField.EditFormLabel] = fmt.Sprintf("%d", createdID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(newFields) > 0 {
|
||||
err = m.repo.BitrixRepo.CheckFields(ctx, newFields, msg.AccountID)
|
||||
if err != nil {
|
||||
m.logger.Error("error updating fields rule in db Check Fields", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
constructFieldRules := func(fieldRuleArrCurrent map[int]string, questions []model.Question, fieldRule *model.BitrixFieldRule, currentEntity model.FieldsType) {
|
||||
ruleMap := make(map[int]string)
|
||||
for questionID, fieldID := range fieldRuleArrCurrent {
|
||||
if fieldID != "" {
|
||||
// если fieldID уже заполнен добавляем его как есть
|
||||
ruleMap[questionID] = fieldID
|
||||
continue
|
||||
}
|
||||
for _, question := range questions {
|
||||
if dataQues, ok := toUpdate[questionID]; ok {
|
||||
if dataQues.Entity == currentEntity {
|
||||
ruleMap[questionID] = dataQues.FieldID
|
||||
break
|
||||
}
|
||||
}
|
||||
if questionID == int(question.Id) {
|
||||
// тут также делаем чтобы сверить филд с вопросом
|
||||
title := strings.ToLower(strings.ReplaceAll(question.Title, " ", ""))
|
||||
if title == "" {
|
||||
question.Title = fmt.Sprintf("Вопрос №%d", question.Page)
|
||||
}
|
||||
title = strings.ToLower(strings.ReplaceAll(question.Title, " ", ""))
|
||||
for _, field := range newFields {
|
||||
fieldName := strings.ToLower(strings.ReplaceAll(field.EditFromLabel, " ", ""))
|
||||
if title == fieldName && field.EntityID == currentEntity {
|
||||
ruleMap[questionID] = field.BitrixID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fieldRule.QuestionID = ruleMap
|
||||
}
|
||||
|
||||
constructFieldRules(currentFieldsRule.Lead.QuestionID, leadQuestions, &lead, model.FieldTypeLead)
|
||||
constructFieldRules(currentFieldsRule.Deal.QuestionID, dealQuestions, &deal, model.FieldTypeDeal)
|
||||
constructFieldRules(currentFieldsRule.Company.QuestionID, companyQuestions, &company, model.FieldTypeCompany)
|
||||
constructFieldRules(currentFieldsRule.Contact.QuestionID, contactQuestions, &contact, model.FieldTypeContact)
|
||||
|
||||
err = m.repo.BitrixRepo.UpdateFieldRules(ctx, model.BitrixFieldRules{
|
||||
Lead: lead,
|
||||
Deal: deal,
|
||||
Company: company,
|
||||
Contact: model.BitrixContactRules{ContactRuleMap: forAdding, QuestionID: contact.QuestionID},
|
||||
}, msg.AccountID, msg.Rule.QuizID)
|
||||
|
||||
if err != nil {
|
||||
m.logger.Error("error updating fields rule in db", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Methods) UserReLogin(ctx context.Context, msg models.KafkaMessage) error {
|
||||
forGetTokens := models.CreateWebHookReq{
|
||||
GrantType: "authorization_code",
|
||||
Code: msg.AuthCode,
|
||||
}
|
||||
|
||||
tokens, err := m.bitrixClient.CreateWebHook(&forGetTokens, true)
|
||||
if err != nil {
|
||||
m.logger.Error("error getting tokens in method user re-login:", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
toUpdate := model.BitrixAccount{
|
||||
AccountID: msg.AccountID,
|
||||
BitrixID: msg.MemberID,
|
||||
Subdomain: msg.RefererURL,
|
||||
}
|
||||
|
||||
err = m.repo.BitrixRepo.UpdateCurrentAccount(ctx, toUpdate)
|
||||
if err != nil {
|
||||
m.logger.Error("error update account in db in method user re-login", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.repo.BitrixRepo.WebhookUpdate(ctx, model.Token{
|
||||
RefreshToken: tokens.RefreshToken,
|
||||
AccessToken: tokens.AccessToken,
|
||||
AccountID: msg.AccountID,
|
||||
Expiration: time.Now().Unix() + tokens.ExpiresIn,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
})
|
||||
if err != nil {
|
||||
m.logger.Error("error update tokens in db in method user re-login", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
939
pkg/bitrixClient/bitrix.go
Normal file
939
pkg/bitrixClient/bitrix.go
Normal file
@ -0,0 +1,939 @@
|
||||
package bitrixClient
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/workers/limiter"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Bitrix struct {
|
||||
fiberClient *fiber.Client
|
||||
logger *zap.Logger
|
||||
redirectionURL string
|
||||
integrationID string
|
||||
integrationSecret string
|
||||
rateLimiter *limiter.RateLimiter
|
||||
fileMutex sync.Mutex
|
||||
}
|
||||
|
||||
type BitrixDeps struct {
|
||||
FiberClient *fiber.Client
|
||||
Logger *zap.Logger
|
||||
RedirectionURL string
|
||||
IntegrationID string
|
||||
IntegrationSecret string
|
||||
RateLimiter *limiter.RateLimiter
|
||||
}
|
||||
|
||||
func NewBitrixClient(deps BitrixDeps) *Bitrix {
|
||||
if deps.FiberClient == nil {
|
||||
deps.FiberClient = fiber.AcquireClient()
|
||||
}
|
||||
return &Bitrix{
|
||||
fiberClient: deps.FiberClient,
|
||||
logger: deps.Logger,
|
||||
redirectionURL: deps.RedirectionURL,
|
||||
integrationSecret: deps.IntegrationSecret,
|
||||
integrationID: deps.IntegrationID,
|
||||
rateLimiter: deps.RateLimiter,
|
||||
}
|
||||
}
|
||||
|
||||
// todo для выполнения некоторых операций нужен определенный скоуп токена надо тоже проверить мою теорию по правам приложения
|
||||
// todo растестить этот запрос пока не проходит
|
||||
// https://dev.1c-bitrix.ru/rest_help/users/user_search.php
|
||||
func (b *Bitrix) GetUserList(accessToken string, domain string) (*models.ResponseGetListUsers, error) {
|
||||
for {
|
||||
if b.rateLimiter.Check() {
|
||||
uri := fmt.Sprintf("https://%s/rest/user.search", domain)
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in GetUserList", zap.Error(err))
|
||||
}
|
||||
return nil, fmt.Errorf("request GetUserList failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("error GetUserList statusCode - %d, respBody - %s", statusCode, string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return nil, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
var userListResponse models.ResponseGetListUsers
|
||||
err := json.Unmarshal(resBody, &userListResponse)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal ResponseGetListUsers:", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &userListResponse, nil
|
||||
}
|
||||
time.Sleep(b.rateLimiter.Interval)
|
||||
}
|
||||
}
|
||||
|
||||
// https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=99&LESSON_ID=2486
|
||||
// https://apidocs.bitrix24.ru/api-reference/oauth/index.html
|
||||
func (b *Bitrix) CreateWebHook(req models.WebHookRequest, tp bool) (*models.CreateWebHookResp, error) {
|
||||
for {
|
||||
if b.rateLimiter.Check() {
|
||||
req.SetClientID(b.integrationID)
|
||||
req.SetClientSecret(b.integrationSecret)
|
||||
var query string
|
||||
|
||||
if tp {
|
||||
query = fmt.Sprintf(
|
||||
"https://oauth.bitrix.info/oauth/token/?grant_type=%s&client_id=%s&client_secret=%s&code=%s",
|
||||
req.GetGrantType(), b.integrationID, b.integrationSecret, req.GetToken(),
|
||||
)
|
||||
} else {
|
||||
query = fmt.Sprintf(
|
||||
"https://oauth.bitrix.info/oauth/token/?grant_type=%s&client_id=%s&client_secret=%s&refresh_token=%s",
|
||||
req.GetGrantType(), b.integrationID, b.integrationSecret, req.GetToken(),
|
||||
)
|
||||
}
|
||||
|
||||
agent := b.fiberClient.Get(query)
|
||||
agent.Set("Content-Type", "application/json")
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in CreateWebHook for create or update tokens", zap.Error(err))
|
||||
}
|
||||
return nil, fmt.Errorf("request failed: %v", errs[0])
|
||||
}
|
||||
|
||||
fmt.Println("CreateWebHook", string(resBody), statusCode)
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("received an incorrect response from CreateWebHook: %s", string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return nil, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
var tokens models.CreateWebHookResp
|
||||
err := json.Unmarshal(resBody, &tokens)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal CreateWebHookResp:", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tokens, nil
|
||||
}
|
||||
time.Sleep(b.rateLimiter.Interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bitrix) GetListSteps(accessToken string, domain string) (*models.StepsResponse, error) {
|
||||
for {
|
||||
if b.rateLimiter.Check() {
|
||||
uri := fmt.Sprintf("https://%s/rest/crm.status.list", domain)
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in GetListSteps", zap.Error(err))
|
||||
}
|
||||
return nil, fmt.Errorf("request GetListSteps failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("error GetListSteps statusCode - %d, respBody - %s", statusCode, string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return nil, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
var result models.StepsResponse
|
||||
err := json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal StepsResponse", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
time.Sleep(b.rateLimiter.Interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bitrix) GetListPipelines(entityTypeID model.IntegerEntityType, accessToken string, domain string) (*models.CategoryResponse, error) {
|
||||
for {
|
||||
if b.rateLimiter.Check() {
|
||||
uri := fmt.Sprintf("https://%s/rest/crm.category.list", domain)
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
agent.Set("Content-Type", "application/json")
|
||||
requestBody := map[string]interface{}{
|
||||
"entityTypeId": entityTypeID,
|
||||
}
|
||||
agent.JSON(requestBody)
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in GetListPipelines", zap.Error(err))
|
||||
}
|
||||
return nil, fmt.Errorf("request GetListPipelines failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("error GetListPipelines statusCode - %d, respBody - %s", statusCode, string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return nil, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
var result models.CategoryResponse
|
||||
err := json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal CategoryResponse", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
time.Sleep(b.rateLimiter.Interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bitrix) GetListFields(fieldType model.FieldsType, accessToken string, domain string) (*models.FieldsResponse, error) {
|
||||
for {
|
||||
if b.rateLimiter.Check() {
|
||||
var listFields models.FieldsResponse
|
||||
switch fieldType {
|
||||
case model.FieldTypeCompany:
|
||||
fullURL := fmt.Sprintf("https://%s/rest/crm.company.userfield.list", domain)
|
||||
agent := b.fiberClient.Post(fullURL)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
requestBody := map[string]interface{}{
|
||||
"order": map[string]string{"SORT": "ASC"},
|
||||
"filter": map[string]string{"LANG": "ru"},
|
||||
}
|
||||
agent.JSON(requestBody)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in GetListFields", zap.Error(err))
|
||||
}
|
||||
return nil, fmt.Errorf("request GetListFields failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("received an incorrect response from GetListFields: %s", string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return nil, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
err := json.Unmarshal(resBody, &listFields)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal models.Company:", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &listFields, nil
|
||||
|
||||
case model.FieldTypeLead:
|
||||
fullURL := fmt.Sprintf("https://%s/rest/crm.lead.userfield.list", domain)
|
||||
agent := b.fiberClient.Post(fullURL)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
requestBody := map[string]interface{}{
|
||||
"order": map[string]string{"SORT": "ASC"},
|
||||
"filter": map[string]string{"LANG": "ru"},
|
||||
}
|
||||
agent.JSON(requestBody)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in GetListFields", zap.Error(err))
|
||||
}
|
||||
return nil, fmt.Errorf("request GetListFields failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("received an incorrect response from GetListFields: %s", string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return nil, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
err := json.Unmarshal(resBody, &listFields)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal models.Lead:", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &listFields, nil
|
||||
|
||||
case model.FieldTypeContact:
|
||||
fullURL := fmt.Sprintf("https://%s/rest/crm.contact.userfield.list", domain)
|
||||
agent := b.fiberClient.Post(fullURL)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
requestBody := map[string]interface{}{
|
||||
"order": map[string]string{"SORT": "ASC"},
|
||||
"filter": map[string]string{"LANG": "ru"},
|
||||
}
|
||||
agent.JSON(requestBody)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in GetListFields", zap.Error(err))
|
||||
}
|
||||
return nil, fmt.Errorf("request GetListFields failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("received an incorrect response from GetListFields: %s", string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return nil, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
err := json.Unmarshal(resBody, &listFields)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal models.Contact:", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &listFields, nil
|
||||
case model.FieldTypeDeal:
|
||||
fullURL := fmt.Sprintf("https://%s/rest/crm.deal.userfield.list", domain)
|
||||
agent := b.fiberClient.Post(fullURL)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
requestBody := map[string]interface{}{
|
||||
"order": map[string]string{"SORT": "ASC"},
|
||||
"filter": map[string]string{"LANG": "ru"},
|
||||
}
|
||||
agent.JSON(requestBody)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in GetListFields", zap.Error(err))
|
||||
}
|
||||
return nil, fmt.Errorf("request GetListFields failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("received an incorrect response from GetListFields: %s", string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return nil, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
err := json.Unmarshal(resBody, &listFields)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal models.Company:", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &listFields, nil
|
||||
}
|
||||
}
|
||||
time.Sleep(b.rateLimiter.Interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bitrix) GetListTags() {
|
||||
|
||||
}
|
||||
|
||||
func (b *Bitrix) GetCurrentUser(accessToken string, domain string) (*models.ResponseGetCurrentUser, error) {
|
||||
for {
|
||||
if b.rateLimiter.Check() {
|
||||
uri := fmt.Sprintf("https://%s/rest/user.current", domain)
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in GetCurrentUser", zap.Error(err))
|
||||
}
|
||||
return nil, fmt.Errorf("request GetCurrentUser failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("error GetCurrentUser statusCode - %d, respBody - %s", statusCode, string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return nil, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
var result models.ResponseGetCurrentUser
|
||||
err := json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal CurrentUser", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
time.Sleep(b.rateLimiter.Interval)
|
||||
}
|
||||
}
|
||||
|
||||
// before neeed call req.GenFieldName()
|
||||
func (b *Bitrix) AddFields(req models.AddFields, entity model.FieldsType, accessToken string, domain string) (int32, error) {
|
||||
for {
|
||||
if b.rateLimiter.Check() {
|
||||
fmt.Println("REQ", req)
|
||||
var result models.MultiResp
|
||||
switch entity {
|
||||
case model.FieldTypeContact:
|
||||
bodyBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
b.logger.Error("error marshal req in Add Fields:", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("https://%s/rest/crm.contact.userfield.add", domain)
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Content-Type", "application/json").Body(bodyBytes)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in AddFields", zap.Error(err))
|
||||
}
|
||||
return 0, fmt.Errorf("request AddFields failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("error AddFields contact statusCode - %d, respBody - %s", statusCode, string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return 0, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal AddFields", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return result.ID, nil
|
||||
case model.FieldTypeCompany:
|
||||
bodyBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
b.logger.Error("error marshal req in Add Fields:", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("https://%s/rest/crm.company.userfield.add", domain)
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Content-Type", "application/json").Body(bodyBytes)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in AddFields", zap.Error(err))
|
||||
}
|
||||
return 0, fmt.Errorf("request AddFields failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("error AddFields company statusCode - %d, respBody - %s", statusCode, string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return 0, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal AddFields", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return result.ID, nil
|
||||
case model.FieldTypeDeal:
|
||||
bodyBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
b.logger.Error("error marshal req in Add Fields:", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("https://%s/rest/crm.deal.userfield.add", domain)
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Content-Type", "application/json").Body(bodyBytes)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in AddFields", zap.Error(err))
|
||||
}
|
||||
return 0, fmt.Errorf("request AddFields failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("error AddFields deal statusCode - %d, respBody - %s", statusCode, string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return 0, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal AddFields", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return result.ID, nil
|
||||
case model.FieldTypeLead:
|
||||
bodyBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
b.logger.Error("error marshal req in Add Fields:", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("https://%s/rest/crm.lead.userfield.add", domain)
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Content-Type", "application/json").Body(bodyBytes)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in AddFields", zap.Error(err))
|
||||
}
|
||||
return 0, fmt.Errorf("request AddFields failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("error AddFields lead statusCode - %d, respBody - %s", statusCode, string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return 0, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal AddFields", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return result.ID, nil
|
||||
}
|
||||
}
|
||||
time.Sleep(b.rateLimiter.Interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bitrix) CreatingDeal(req map[string]map[string]interface{}, accessToken string, domain string) (int32, error) {
|
||||
for {
|
||||
if b.rateLimiter.Check() {
|
||||
uri := fmt.Sprintf("https://%s/rest/crm.deal.add", domain)
|
||||
bodyBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
b.logger.Error("error marshal req in Creating Deal:", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Content-Type", "application/json").Body(bodyBytes)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err = range errs {
|
||||
b.logger.Error("error sending request in Creating Deal", zap.Error(err))
|
||||
}
|
||||
return 0, fmt.Errorf("request failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("received an incorrect response from Creating Deal: %s", string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return 0, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
var resp models.MultiResp
|
||||
err = json.Unmarshal(resBody, &resp)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal response body in Creating Deal:", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return resp.ID, nil
|
||||
}
|
||||
time.Sleep(b.rateLimiter.Interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bitrix) CreateCompany(req map[string]map[string]interface{}, accessToken string, domain string) (int32, error) {
|
||||
for {
|
||||
if b.rateLimiter.Check() {
|
||||
uri := fmt.Sprintf("https://%s/rest/crm.company.add", domain)
|
||||
bodyBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
b.logger.Error("error marshal req in Creating Company:", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Content-Type", "application/json").Body(bodyBytes)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err = range errs {
|
||||
b.logger.Error("error sending request in Creating Company", zap.Error(err))
|
||||
}
|
||||
return 0, fmt.Errorf("request failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("received an incorrect response from Creating Company: %s", string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return 0, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
var result models.MultiResp
|
||||
err = json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal response body in Creating Company:", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return result.ID, nil
|
||||
}
|
||||
time.Sleep(b.rateLimiter.Interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bitrix) CreateContact(req map[string]map[string]interface{}, accessToken string, domain string) (int32, error) {
|
||||
for {
|
||||
if b.rateLimiter.Check() {
|
||||
uri := fmt.Sprintf("https://%s/rest/crm.contact.add", domain)
|
||||
bodyBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
b.logger.Error("error marshal req in Creating Contact:", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Content-Type", "application/json").Body(bodyBytes)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err = range errs {
|
||||
b.logger.Error("error sending request in Creating Contact", zap.Error(err))
|
||||
}
|
||||
return 0, fmt.Errorf("request failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("received an incorrect response from Creating Contact: %s", string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return 0, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
var result models.MultiResp
|
||||
err = json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal response body in Creating Contact:", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return result.ID, nil
|
||||
}
|
||||
time.Sleep(b.rateLimiter.Interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bitrix) UpdateContact(req map[string]map[string]interface{}, accessToken string, domain string, contactID int32) error {
|
||||
for {
|
||||
if b.rateLimiter.Check() {
|
||||
uri := fmt.Sprintf("https://%s/rest/crm.contact.update?ID=%d", domain, contactID)
|
||||
bodyBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
b.logger.Error("error marshal req in Updating Contact:", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Content-Type", "application/json").Body(bodyBytes)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err = range errs {
|
||||
b.logger.Error("error sending request in Updating Contact", zap.Error(err))
|
||||
}
|
||||
return fmt.Errorf("request failed: %v", errs[0])
|
||||
}
|
||||
|
||||
b.logger.Info("received response from Updating Contact:", zap.String("resBody", string(resBody)))
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("received an incorrect response from Creating Contact: %s", string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
time.Sleep(b.rateLimiter.Interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bitrix) CreatingLead(req map[string]map[string]interface{}, accessToken string, domain string) (int32, error) {
|
||||
for {
|
||||
if b.rateLimiter.Check() {
|
||||
uri := fmt.Sprintf("https://%s/rest/crm.lead.add", domain)
|
||||
bodyBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
b.logger.Error("error marshal req in Creating Lead:", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Content-Type", "application/json").Body(bodyBytes)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err = range errs {
|
||||
b.logger.Error("error sending request in Creating Lead", zap.Error(err))
|
||||
return 0, fmt.Errorf("request failed: %v", errs[0])
|
||||
}
|
||||
}
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("received an incorrect response from Creating Lead: %s", string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return 0, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
var result models.MultiResp
|
||||
err = json.Unmarshal(resBody, &result)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal response body in Creating Lead:", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return result.ID, nil
|
||||
}
|
||||
time.Sleep(b.rateLimiter.Interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bitrix) GetCustomFieldByID(fieldID int32, fieldType model.FieldsType, accessToken string, domain string) (*models.Fields, error) {
|
||||
for {
|
||||
if b.rateLimiter.Check() {
|
||||
var resp models.Fields
|
||||
var err error
|
||||
|
||||
switch fieldType {
|
||||
case model.FieldTypeContact:
|
||||
uri := fmt.Sprintf("https://%s/rest/crm.contact.userfield.get?ID=%d", domain, fieldID)
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Content-Type", "application/json")
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in GetCustomFieldByID", zap.Error(err))
|
||||
}
|
||||
return nil, fmt.Errorf("request GetCustomFieldByID failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("error GetCustomFieldByID contact statusCode - %d, respBody - %s", statusCode, string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return nil, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &resp)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal GetCustomFieldByID", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
case model.FieldTypeCompany:
|
||||
uri := fmt.Sprintf("https://%s/rest/crm.company.userfield.get?ID=%d", domain, fieldID)
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Content-Type", "application/json")
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in GetCustomFieldByID", zap.Error(err))
|
||||
}
|
||||
return nil, fmt.Errorf("request GetCustomFieldByID failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("error GetCustomFieldByID company statusCode - %d, respBody - %s", statusCode, string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return nil, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &resp)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal GetCustomFieldByID", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
case model.FieldTypeDeal:
|
||||
uri := fmt.Sprintf("https://%s/rest/crm.deal.userfield.get?ID=%d", domain, fieldID)
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Content-Type", "application/json")
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in GetCustomFieldByID", zap.Error(err))
|
||||
}
|
||||
return nil, fmt.Errorf("request GetCustomFieldByID failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("error GetCustomFieldByID deal statusCode - %d, respBody - %s", statusCode, string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return nil, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &resp)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal GetCustomFieldByID", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
case model.FieldTypeLead:
|
||||
uri := fmt.Sprintf("https://%s/rest/crm.lead.userfield.get?ID=%d", domain, fieldID)
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Content-Type", "application/json")
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request in GetCustomFieldByID", zap.Error(err))
|
||||
}
|
||||
return nil, fmt.Errorf("request GetCustomFieldByID failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("error GetCustomFieldByID lead statusCode - %d, respBody - %s", statusCode, string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return nil, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(resBody, &resp)
|
||||
if err != nil {
|
||||
b.logger.Error("error unmarshal AddFields", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
}
|
||||
time.Sleep(b.rateLimiter.Interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bitrix) DownLoadFile(urlFile string) (string, string, error) {
|
||||
var err error
|
||||
agent := b.fiberClient.Get(urlFile)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err = range errs {
|
||||
b.logger.Error("error sending request for getting file by url", zap.Error(err))
|
||||
}
|
||||
return "", "", fmt.Errorf("request failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("received an incorrect response from getting file by url: %s", string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return "", "", fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
fileName := strings.Split(urlFile, "/")
|
||||
|
||||
resBodyBase64 := base64.StdEncoding.EncodeToString(resBody)
|
||||
|
||||
return fileName[len(fileName)-1], resBodyBase64, nil
|
||||
}
|
||||
|
||||
// scope crm do not have privileges
|
||||
func (b *Bitrix) AddedTagCustomField(accessToken string, domain string) error {
|
||||
for {
|
||||
if b.rateLimiter.Check() {
|
||||
uri := fmt.Sprintf("https://%s/rest/userfieldtype.add", domain)
|
||||
|
||||
req := models.UserFieldTypeAddReq{
|
||||
UserTypeID: string(model.PenaTagCustomFieldsType),
|
||||
Handler: "https://b24-s5jg6c.bitrix24.ru", // todo заменить на что то реальное
|
||||
Title: "Кастомный тип пользовательского поля Pena",
|
||||
Description: "Кастомный тип пользовательского поля Pena",
|
||||
}
|
||||
|
||||
bodyBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
b.logger.Error("error marshalling body", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
agent := b.fiberClient.Post(uri)
|
||||
agent.Set("Content-Type", "application/json").Body(bodyBytes)
|
||||
agent.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error sending request for AddedTagCustomField", zap.Error(err))
|
||||
}
|
||||
return fmt.Errorf("request failed: %v", errs[0])
|
||||
}
|
||||
fmt.Println(string(resBody))
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("received an incorrect response from AddedTagCustomField: %s", string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return fmt.Errorf("request failed: %v", errs[0])
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
time.Sleep(b.rateLimiter.Interval)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bitrix) CheckScope(token, domain string) (any, error) {
|
||||
for {
|
||||
if b.rateLimiter.Check() {
|
||||
uri := fmt.Sprintf("https://%s/rest/scope?auth=%s", domain, token)
|
||||
|
||||
agent := b.fiberClient.Get(uri)
|
||||
|
||||
statusCode, resBody, errs := agent.Bytes()
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
b.logger.Error("error check scope", zap.Error(err))
|
||||
}
|
||||
return 0, fmt.Errorf("request failed: %v", errs[0])
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
errorMessage := fmt.Sprintf("received an incorrect response from check scope: %s", string(resBody))
|
||||
b.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||
return 0, fmt.Errorf(errorMessage)
|
||||
}
|
||||
|
||||
return string(resBody), nil
|
||||
}
|
||||
time.Sleep(b.rateLimiter.Interval)
|
||||
}
|
||||
}
|
280
pkg/bitrixClient/bitrix_test.go
Normal file
280
pkg/bitrixClient/bitrix_test.go
Normal file
@ -0,0 +1,280 @@
|
||||
package bitrixClient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/workers/limiter"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGetListFields(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
lim := limiter.NewRateLimiter(ctx, 50, 2*time.Second)
|
||||
logger := zap.NewNop()
|
||||
b := NewBitrixClient(BitrixDeps{
|
||||
Logger: logger,
|
||||
RedirectionURL: "test",
|
||||
IntegrationID: "test",
|
||||
IntegrationSecret: "test",
|
||||
RateLimiter: lim,
|
||||
})
|
||||
|
||||
//result, err := b.GetUserList("262df9660000071b00717f9200000001000007c9148fd5a4211fc98142ea9bc41fc8d3", "b24-ld76ub.bitrix24.ru")
|
||||
//if err != nil {
|
||||
// t.Fatal(err)
|
||||
//}
|
||||
//fmt.Println(result)
|
||||
|
||||
//arr := []model.FieldsType{model.FieldTypeLead, model.FieldTypeCompany, model.FieldTypeContact, model.FieldTypeDeal}
|
||||
//
|
||||
//for i, tipe := range arr {
|
||||
// req := models.AddFields{
|
||||
// EditFormLabel: fmt.Sprintf("EditFormLabel %d", i),
|
||||
// ListColumnLabel: fmt.Sprintf("ListColumnLabel %d", i),
|
||||
// UserTypeID: "string",
|
||||
// Settings: map[string]interface{}{
|
||||
// "DEFAULT_VALUE": "GOGOGOGOGOGOGO!",
|
||||
// }}
|
||||
// req.GenFieldName()
|
||||
// result, err := b.AddFields(req, tipe, "9c7cf1660000071b00717f9200000001000007b3c27dd12d61d2e90dd1e630638b8346", "b24-ld76ub.bitrix24.ru")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// fmt.Println(result)
|
||||
//}
|
||||
//
|
||||
fieldAnswer := make(map[string]string)
|
||||
fieldAnswer["UF_CRM_1729778229491"] = "хуккккк"
|
||||
|
||||
//
|
||||
//createContactReq := models.CreateContactReq{
|
||||
// Fields: models.ContactFields{
|
||||
// Name: "Контакт фром апи",
|
||||
// SecondName: "SecondName",
|
||||
// LastName: "LastName",
|
||||
// Opened: "Y",
|
||||
// LeadID: 1,
|
||||
// UtmSource: "UtmSource",
|
||||
// UtmMedium: "UtmMedium",
|
||||
// UtmCampaign: "UtmCampaign",
|
||||
// UtmContent: "UtmContent",
|
||||
// UtmTerm: "UtmTerm",
|
||||
// },
|
||||
//}
|
||||
//
|
||||
//reqMap := models.FormattingToMap(&createContactReq, fieldAnswer)
|
||||
//
|
||||
//contactResult, err := b.CreateContact(reqMap, "ad5df5660000071b00717f920000000100000783b6e19c671e64c3655e5c7aff197e14", "b24-ld76ub.bitrix24.ru")
|
||||
//if err != nil {
|
||||
// fmt.Println(err)
|
||||
//}
|
||||
//
|
||||
//createCompanyReq := models.CompanyReq{
|
||||
// Fields: models.CompanyFields{
|
||||
// Title: "TEST FORMATTER",
|
||||
// Opened: "Y",
|
||||
// LeadID: 1,
|
||||
// UtmSource: "UtmSource",
|
||||
// UtmMedium: "UtmMedium",
|
||||
// UtmCampaign: "UtmCampaign",
|
||||
// UtmContent: "UtmContent",
|
||||
// UtmTerm: "UtmTerm",
|
||||
// ContactID: contactResult,
|
||||
// },
|
||||
//}
|
||||
//
|
||||
//reqMap = models.FormattingToMap(&createCompanyReq, fieldAnswer)
|
||||
//companyResult, err := b.CreateCompany(reqMap, "ad5df5660000071b00717f920000000100000783b6e19c671e64c3655e5c7aff197e14", "b24-ld76ub.bitrix24.ru")
|
||||
//if err != nil {
|
||||
// fmt.Println(err)
|
||||
//}
|
||||
//
|
||||
createDealReq := models.CreatingDealReq{
|
||||
Fields: models.CreateDealFields{
|
||||
Title: "ТЕСТ ОТ ГОУ АПИ 10/10",
|
||||
TypeID: "SALE",
|
||||
StageID: "NEW",
|
||||
CompanyID: 383,
|
||||
ContactIDs: []int32{235},
|
||||
Opened: "Y",
|
||||
AssignedByID: 1,
|
||||
CategoryID: 1,
|
||||
SourceID: "CALL",
|
||||
UtmSource: "UtmSource",
|
||||
UtmMedium: "UtmMedium",
|
||||
UtmCampaign: "UtmCampaign",
|
||||
UtmContent: "UtmContent",
|
||||
UtmTerm: "UtmTerm",
|
||||
},
|
||||
}
|
||||
//
|
||||
reqMap := models.FormattingToMap(&createDealReq, fieldAnswer)
|
||||
fmt.Println(reqMap)
|
||||
result, err := b.CreatingDeal(reqMap, "e16b1a670000071b007254120000000100000759485bd49a2206c79f84e8a150522f43", "b24-s5jg6c.bitrix24.ru")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(result)
|
||||
//
|
||||
//for _, tipe := range model.CategoryArr {
|
||||
// result, err := b.GetListPipelines(tipe, "9d5bf9660000071b00717f9200000001000007b8da5b64a2142c5a0abcfb3e65f89b0c", "b24-ld76ub.bitrix24.ru")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
//
|
||||
// r, _ := json.Marshal(result)
|
||||
// fmt.Println(string(r))
|
||||
//}
|
||||
//
|
||||
//for _, tipe := range arr {
|
||||
// result, err := b.GetListFields(tipe, "07cff6660000071b00717f92000000010000079e5af88b052dbcfe9e9d98cac38710ad", "b24-ld76ub.bitrix24.ru")
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
//
|
||||
// r, _ := json.Marshal(result)
|
||||
// fmt.Println(string(r))
|
||||
// fmt.Println(tipe)
|
||||
//}
|
||||
//arr2 := []model.TypeStepsEntityID{model.StatusStepsEntityID, model.DealTypeStepsEntityID, model.DealStageStepsEntityID, model.SourceStepsEntityID, model.ContactTypeStepsEntityID, model.CompanyTypeStepsEntityID, model.EmployeesStepsEntityID, model.IndustryStepsEntityID, model.SmartInvoiceStageStepsEntityID, model.QuoteStatusStepsEntityID, model.HonorificStepsEntityID, model.CallListStepsEntityID, model.SmartDocumentStageStepsEntityID}
|
||||
//for _, stepType := range arr2 {
|
||||
//result, err := b.GetListSteps("9d5bf9660000071b00717f9200000001000007b8da5b64a2142c5a0abcfb3e65f89b0c", "b24-ld76ub.bitrix24.ru")
|
||||
//if err != nil {
|
||||
// fmt.Println(err)
|
||||
//}
|
||||
//for _, i := range result.Result {
|
||||
// fmt.Println(i.ID)
|
||||
//}
|
||||
//}
|
||||
//
|
||||
//"CATEGORY_ID":"1"
|
||||
}
|
||||
|
||||
func Test_Auth(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
lim := limiter.NewRateLimiter(ctx, 50, 2*time.Second)
|
||||
logger := zap.NewNop()
|
||||
b := NewBitrixClient(BitrixDeps{
|
||||
Logger: logger,
|
||||
RedirectionURL: "https://squiz.pena.digital/integrations",
|
||||
IntegrationID: "app.670bd825e44c52.61826940",
|
||||
IntegrationSecret: "Ki0MElZXS6dE6tRsGxixri2jmxbxF2Xa4qQpBPziGdAvvLAHJx",
|
||||
RateLimiter: lim,
|
||||
})
|
||||
|
||||
tokens, err := b.CreateWebHook(&models.CreateWebHookReq{
|
||||
GrantType: "authorization_code",
|
||||
Code: "50cb1067007232200072541200000001000007aab0e419e6de4ebff7dd4b238c144bae",
|
||||
}, true)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(tokens)
|
||||
}
|
||||
|
||||
func Test_GetListUsers(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
lim := limiter.NewRateLimiter(ctx, 50, 2*time.Second)
|
||||
logger := zap.NewNop()
|
||||
b := NewBitrixClient(BitrixDeps{
|
||||
Logger: logger,
|
||||
RedirectionURL: "https://squiz.pena.digital/integrations",
|
||||
IntegrationID: "app.670bd825e44c52.61826940",
|
||||
IntegrationSecret: "Ki0MElZXS6dE6tRsGxixri2jmxbxF2Xa4qQpBPziGdAvvLAHJx",
|
||||
RateLimiter: lim,
|
||||
})
|
||||
|
||||
r, err := b.GetUserList("664e12670000071b00725412000000010000071da5594e25e0731541e2bd2ea0d78a7b", "b24-s5jg6c.bitrix24.ru")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(r)
|
||||
}
|
||||
|
||||
func Test_Add_Fields(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
lim := limiter.NewRateLimiter(ctx, 50, 2*time.Second)
|
||||
logger := zap.NewNop()
|
||||
b := NewBitrixClient(BitrixDeps{
|
||||
Logger: logger,
|
||||
RedirectionURL: "https://squiz.pena.digital/integrations",
|
||||
IntegrationID: "app.670bd825e44c52.61826940",
|
||||
IntegrationSecret: "Ki0MElZXS6dE6tRsGxixri2jmxbxF2Xa4qQpBPziGdAvvLAHJx",
|
||||
RateLimiter: lim,
|
||||
})
|
||||
|
||||
req := models.AddFields{
|
||||
FieldName: "Test_Add_Fields_Deal",
|
||||
EditFormLabel: "Test_Add_Fields_Deal",
|
||||
ListColumnLabel: "Test_Add_Fields_Deal",
|
||||
UserTypeID: model.StringCustomFieldsType,
|
||||
}
|
||||
|
||||
req.GenFieldName()
|
||||
|
||||
resp, err := b.AddFields(req, model.FieldTypeDeal, "9afc14670072322000725412000000010000074c130dd22fbea5321c68aebd7e4e6b98", "b24-s5jg6c.bitrix24.ru")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(resp)
|
||||
}
|
||||
|
||||
func Test_Update_Contact(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
lim := limiter.NewRateLimiter(ctx, 50, 2*time.Second)
|
||||
logger := zap.NewNop()
|
||||
b := NewBitrixClient(BitrixDeps{
|
||||
Logger: logger,
|
||||
RedirectionURL: "https://squiz.pena.digital/integrations",
|
||||
IntegrationID: "app.670bd825e44c52.61826940",
|
||||
IntegrationSecret: "Ki0MElZXS6dE6tRsGxixri2jmxbxF2Xa4qQpBPziGdAvvLAHJx",
|
||||
RateLimiter: lim,
|
||||
})
|
||||
|
||||
contactFields := make(map[string]string)
|
||||
contactFields["UF_CRM_1729426581"] = "phoneTESTUPDATE"
|
||||
contactFields["UF_CRM_1729425300"] = "emailTESTUPDATE"
|
||||
contactFields["UF_CRM_1729425184"] = "nameTESTUPDATE"
|
||||
|
||||
reqContact := models.CreateContactReq{
|
||||
Fields: models.ContactFields{
|
||||
Name: "TEST UPDATE CONTACT",
|
||||
},
|
||||
}
|
||||
|
||||
reqMapContact := models.FormattingToMap(&reqContact, contactFields)
|
||||
|
||||
err := b.UpdateContact(reqMapContact, "6b2c16670000071b0072541200000001000007b21dd57044f7de29fde6a9566e6932f1", "b24-s5jg6c.bitrix24.ru", 53)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println("GOOD")
|
||||
}
|
||||
|
||||
func Test_AddedTagCustomField(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
lim := limiter.NewRateLimiter(ctx, 50, 2*time.Second)
|
||||
logger := zap.NewNop()
|
||||
b := NewBitrixClient(BitrixDeps{
|
||||
Logger: logger,
|
||||
RedirectionURL: "https://squiz.pena.digital/integrations",
|
||||
IntegrationID: "app.670bd825e44c52.61826940",
|
||||
IntegrationSecret: "Ki0MElZXS6dE6tRsGxixri2jmxbxF2Xa4qQpBPziGdAvvLAHJx",
|
||||
RateLimiter: lim,
|
||||
})
|
||||
|
||||
err := b.AddedTagCustomField("e16b1a670000071b007254120000000100000759485bd49a2206c79f84e8a150522f43", "b24-s5jg6c.bitrix24.ru")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println("GOOD")
|
||||
}
|
37
pkg/closer/closer.go
Normal file
37
pkg/closer/closer.go
Normal file
@ -0,0 +1,37 @@
|
||||
package closer
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type Closer interface {
|
||||
Close(ctx context.Context) error
|
||||
}
|
||||
|
||||
type CloserFunc func(ctx context.Context) error
|
||||
|
||||
func (cf CloserFunc) Close(ctx context.Context) error {
|
||||
return cf(ctx)
|
||||
}
|
||||
|
||||
type CloserGroup struct {
|
||||
closers []Closer
|
||||
}
|
||||
|
||||
func NewCloserGroup() *CloserGroup {
|
||||
return &CloserGroup{}
|
||||
}
|
||||
|
||||
func (cg *CloserGroup) Add(c Closer) {
|
||||
cg.closers = append(cg.closers, c)
|
||||
}
|
||||
|
||||
func (cg *CloserGroup) Call(ctx context.Context) error {
|
||||
var closeErr error
|
||||
for i := len(cg.closers) - 1; i >= 0; i-- {
|
||||
if err := cg.closers[i].Close(ctx); err != nil && closeErr == nil {
|
||||
closeErr = err
|
||||
}
|
||||
}
|
||||
return closeErr
|
||||
}
|
Loading…
Reference in New Issue
Block a user