Merge branch 'staging'
This commit is contained in:
commit
76acdc6499
162
.gitignore
vendored
Normal file
162
.gitignore
vendored
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
# 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
|
53
.gitlab-ci.yml
Normal file
53
.gitlab-ci.yml
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
include:
|
||||||
|
- project: "devops/pena-continuous-integration"
|
||||||
|
file: "/templates/docker/build-template.gitlab-ci.yml"
|
||||||
|
- project: "devops/pena-continuous-integration"
|
||||||
|
file: "/templates/docker/deploy-template.gitlab-ci.yml"
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
- migrate
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
build-app:
|
||||||
|
stage: build
|
||||||
|
extends: .build_template
|
||||||
|
rules:
|
||||||
|
- if: "$CI_COMMIT_BRANCH == $STAGING_BRANCH || $CI_COMMIT_BRANCH == $PRODUCTION_BRANCH"
|
||||||
|
script:
|
||||||
|
- docker build -t $CI_REGISTRY_IMAGE/$CI_COMMIT_BRANCH:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID --build-arg GITLAB_TOKEN=$GITLAB_TOKEN $CI_PROJECT_DIR
|
||||||
|
- docker push $CI_REGISTRY_IMAGE/$CI_COMMIT_BRANCH:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||||
|
|
||||||
|
migrate-staging:
|
||||||
|
stage: migrate
|
||||||
|
variables:
|
||||||
|
STAGING_BRANCH: staging
|
||||||
|
tags:
|
||||||
|
- staging
|
||||||
|
rules:
|
||||||
|
- if: "$CI_COMMIT_BRANCH == $STAGING_BRANCH"
|
||||||
|
script:
|
||||||
|
- apk add git
|
||||||
|
- git clone https://buildToken:glpat-axA8ttckx3aPf_xd2Dym@penahub.gitlab.yandexcloud.net/backend/quiz/common.git
|
||||||
|
- ls
|
||||||
|
- ./tools/migrate -source file://common/dal/schema -database postgres://squiz:Redalert2@10.8.0.5:5433/squiz?sslmode=disable up
|
||||||
|
|
||||||
|
deploy-staging:
|
||||||
|
stage: deploy
|
||||||
|
tags:
|
||||||
|
- staging
|
||||||
|
extends: .deploy_template
|
||||||
|
rules:
|
||||||
|
- if: "$CI_COMMIT_BRANCH == $STAGING_BRANCH"
|
||||||
|
after_script:
|
||||||
|
- docker ps -a
|
||||||
|
|
||||||
|
deploy-prod:
|
||||||
|
stage: deploy
|
||||||
|
tags:
|
||||||
|
- prod
|
||||||
|
extends: .deploy_template
|
||||||
|
rules:
|
||||||
|
- if: "$CI_COMMIT_BRANCH == $PRODUCTION_BRANCH"
|
||||||
|
after_script:
|
||||||
|
- ls
|
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
FROM dockerhub.timeweb.cloud/golang:alpine as build
|
||||||
|
WORKDIR /app
|
||||||
|
RUN apk add git
|
||||||
|
COPY . .
|
||||||
|
ARG GITLAB_TOKEN
|
||||||
|
ENV GOPRIVATE=penahub.gitlab.yandexcloud.net/backend/penahub_common
|
||||||
|
RUN git config --global url."https://buildToken:glpat-axA8ttckx3aPf_xd2Dym@penahub.gitlab.yandexcloud.net/".insteadOf "https://penahub.gitlab.yandexcloud.net/"
|
||||||
|
RUN go mod download
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o amocrm ./cmd/main.go
|
||||||
|
|
||||||
|
FROM penahub.gitlab.yandexcloud.net:5050/devops/dockerhub-backup/alpine as prod
|
||||||
|
COPY --from=build /app/amocrm .
|
||||||
|
EXPOSE 1488
|
||||||
|
ENV IS_PROD_LOG=false
|
||||||
|
ENV IS_PROD=false
|
||||||
|
CMD ["/amocrm"]
|
20
blueprint.yaml
Normal file
20
blueprint.yaml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
templateProjectName: amocrm
|
||||||
|
Description: Service for integration with amocrm
|
||||||
|
|
||||||
|
Template:
|
||||||
|
path: "./"
|
||||||
|
|
||||||
|
Modules:
|
||||||
|
logger:
|
||||||
|
name: zap
|
||||||
|
env:
|
||||||
|
vars:
|
||||||
|
- name: APP_NAME
|
||||||
|
type: string
|
||||||
|
default: "{{.ProjectName}}"
|
||||||
|
openapi:
|
||||||
|
model_save_path: ./internal/models
|
||||||
|
controller_save_path: ./internal/controllers
|
||||||
|
service_save_path: ./internal/service
|
||||||
|
repository_save_path: ./internal/repository
|
||||||
|
server_save_path: ./internal/server/http
|
34
cmd/main.go
Normal file
34
cmd/main.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/initialize"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"amocrm/internal/app"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
87
database.puml
Normal file
87
database.puml
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
@startuml Database
|
||||||
|
|
||||||
|
map FieldRule {
|
||||||
|
QuestionID => **integer**
|
||||||
|
}
|
||||||
|
|
||||||
|
map UTM {
|
||||||
|
CreatedAt => **integer**
|
||||||
|
Deleted => **boolean**
|
||||||
|
ID => **integer**
|
||||||
|
Name => **string**
|
||||||
|
QuizID => **integer**
|
||||||
|
AccountID => **string**
|
||||||
|
AmoFieldID => **integer**
|
||||||
|
}
|
||||||
|
|
||||||
|
map Pipeline {
|
||||||
|
CreatedAt => **integer**
|
||||||
|
Deleted => **boolean**
|
||||||
|
ID => **integer**
|
||||||
|
IsArchive => **boolean**
|
||||||
|
Name => **string**
|
||||||
|
AccountID => **string**
|
||||||
|
AmoID => **integer**
|
||||||
|
}
|
||||||
|
|
||||||
|
map Step {
|
||||||
|
Deleted => **boolean**
|
||||||
|
ID => **integer**
|
||||||
|
Name => **string**
|
||||||
|
PipelineID => **integer**
|
||||||
|
AccountID => **string**
|
||||||
|
AmoID => **integer**
|
||||||
|
Color => **string**
|
||||||
|
CreatedAt => **integer**
|
||||||
|
}
|
||||||
|
|
||||||
|
map Tag {
|
||||||
|
AccountID => **string**
|
||||||
|
AmoID => **integer**
|
||||||
|
Color => **string**
|
||||||
|
CreatedAt => **integer**
|
||||||
|
Deleted => **boolean**
|
||||||
|
Entity => **string**
|
||||||
|
ID => **integer**
|
||||||
|
Name => **string**
|
||||||
|
}
|
||||||
|
|
||||||
|
map Field {
|
||||||
|
CreatedAt => **integer**
|
||||||
|
Deleted => **boolean**
|
||||||
|
EntityType => **string**
|
||||||
|
ID => **integer**
|
||||||
|
Name => **string**
|
||||||
|
Type => **string**
|
||||||
|
AccountID => **string**
|
||||||
|
AmoID => **integer**
|
||||||
|
Code => **string**
|
||||||
|
}
|
||||||
|
|
||||||
|
map User {
|
||||||
|
Role => **string**
|
||||||
|
AmoID => **integer**
|
||||||
|
Group => **string**
|
||||||
|
Name => **string**
|
||||||
|
Email => **string**
|
||||||
|
ID => **integer**
|
||||||
|
AccountID => **string**
|
||||||
|
CreatedAt => **integer**
|
||||||
|
Deleted => **boolean**
|
||||||
|
}
|
||||||
|
|
||||||
|
map Rule {
|
||||||
|
CreatedAt => **integer**
|
||||||
|
Deleted => **boolean**
|
||||||
|
FieldsRule => **FieldsRule**
|
||||||
|
PipelineID => **integer**
|
||||||
|
QuizID => **integer**
|
||||||
|
StepID => **integer**
|
||||||
|
UTMs => **[]integer**
|
||||||
|
AccountID => **string**
|
||||||
|
PerformerID => **integer**
|
||||||
|
ID => **integer**
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@enduml
|
12
deployments/local/docker-compose.yaml
Normal file
12
deployments/local/docker-compose.yaml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: Redalert2
|
||||||
|
POSTGRES_USER: squiz
|
||||||
|
POSTGRES_DB: squiz
|
||||||
|
app:
|
||||||
|
image: penahub.gitlab.yandexcloud.net:5050/backend/squiz:latest
|
||||||
|
ports:
|
||||||
|
- 1488:1488
|
25
deployments/main/docker-compose.yaml
Normal file
25
deployments/main/docker-compose.yaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
amocrm:
|
||||||
|
hostname: squiz-amocrm
|
||||||
|
container_name: squiz-amocrm
|
||||||
|
image: $CI_REGISTRY_IMAGE/main:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||||
|
tty: true
|
||||||
|
environment:
|
||||||
|
HTTP_HOST: '0.0.0.0'
|
||||||
|
HTTP_PORT: 1488
|
||||||
|
REDIS_ADDR: '10.8.0.9:6379'
|
||||||
|
REDIS_PASS: 'Redalert2'
|
||||||
|
REDIS_DB: 4
|
||||||
|
PENA_SOCIAL_AUTH_URL: 'http://10.8.0.8:59344/amocrm/auth'
|
||||||
|
|
||||||
|
PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY
|
||||||
|
PG_CRED: 'host=10.8.0.9 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable'
|
||||||
|
PUBLIC_KEY: $PEM_PUB_USERID
|
||||||
|
PRIVATE_KEY: $PEM_PRIV_USERID
|
||||||
|
KAFKA_BROKERS: 10.8.0.8:9092
|
||||||
|
KAFKA_TOPIC: "squiz-amocrm"
|
||||||
|
GRPC_HOST: "0.0.0.0"
|
||||||
|
REDIRECT_URL: "https://quiz.pena.digital/integrations"
|
||||||
|
ports:
|
||||||
|
- 10.8.0.9:1492:1488
|
17
deployments/main/staging/docker-compose.yaml
Normal file
17
deployments/main/staging/docker-compose.yaml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
services:
|
||||||
|
core:
|
||||||
|
hostname: squiz-core
|
||||||
|
container_name: squiz-core
|
||||||
|
image: $CI_REGISTRY_IMAGE/core:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||||
|
tty: true
|
||||||
|
environment:
|
||||||
|
HUB_ADMIN_URL: 'http://10.6.0.11:59303'
|
||||||
|
IS_PROD_LOG: 'false'
|
||||||
|
IS_PROD: 'false'
|
||||||
|
PORT: 1488
|
||||||
|
PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY
|
||||||
|
PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable'
|
||||||
|
AUTH_URL: 'http://10.6.0.11:59300/user'
|
||||||
|
ports:
|
||||||
|
- 1488:1488
|
||||||
|
|
25
deployments/staging/docker-compose.yaml
Normal file
25
deployments/staging/docker-compose.yaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
amocrm:
|
||||||
|
hostname: squiz-amocrm
|
||||||
|
container_name: squiz-amocrm
|
||||||
|
image: $CI_REGISTRY_IMAGE/staging:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||||
|
tty: true
|
||||||
|
environment:
|
||||||
|
HTTP_HOST: '0.0.0.0'
|
||||||
|
HTTP_PORT: 1488
|
||||||
|
REDIS_ADDR: '10.8.0.5:6379'
|
||||||
|
REDIS_PASS: 'Redalert2'
|
||||||
|
REDIS_DB: 4
|
||||||
|
PENA_SOCIAL_AUTH_URL: 'http://10.8.0.6:59344/amocrm/auth'
|
||||||
|
|
||||||
|
PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY
|
||||||
|
PG_CRED: 'host=10.8.0.5 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable'
|
||||||
|
PUBLIC_KEY: $PEM_PUB_USERID
|
||||||
|
PRIVATE_KEY: $PEM_PRIV_USERID
|
||||||
|
KAFKA_BROKERS: 10.8.0.6:9092
|
||||||
|
KAFKA_TOPIC: "squiz-amocrm"
|
||||||
|
GRPC_HOST: "0.0.0.0"
|
||||||
|
REDIRECT_URL: "https://squiz.pena.digital/integrations"
|
||||||
|
ports:
|
||||||
|
- 10.8.0.5:1492:1488
|
102
deployments/test/docker-compose.yaml
Normal file
102
deployments/test/docker-compose.yaml
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
test-postgres:
|
||||||
|
image: postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: Redalert2
|
||||||
|
POSTGRES_USER: squiz
|
||||||
|
POSTGRES_DB: squiz
|
||||||
|
volumes:
|
||||||
|
- test-postgres:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- 35432:5432
|
||||||
|
networks:
|
||||||
|
- penatest
|
||||||
|
healthcheck:
|
||||||
|
test: pg_isready -U squiz
|
||||||
|
interval: 2s
|
||||||
|
timeout: 2s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
# need update!
|
||||||
|
# test-pena-auth-service:
|
||||||
|
# image: penahub.gitlab.yandexcloud.net:5050/pena-services/pena-auth-service:staging.872
|
||||||
|
# container_name: test-pena-auth-service
|
||||||
|
# init: true
|
||||||
|
# env_file: auth.env.test
|
||||||
|
# healthcheck:
|
||||||
|
# test: wget -T1 --spider http://localhost:8000/user
|
||||||
|
# interval: 2s
|
||||||
|
# timeout: 2s
|
||||||
|
# retries: 5
|
||||||
|
# environment:
|
||||||
|
# - DB_HOST=test-pena-auth-db
|
||||||
|
# - DB_PORT=27017
|
||||||
|
# - ENVIRONMENT=staging
|
||||||
|
# - HTTP_HOST=0.0.0.0
|
||||||
|
# - HTTP_PORT=8000
|
||||||
|
# - DB_USERNAME=test
|
||||||
|
# - DB_PASSWORD=test
|
||||||
|
# - DB_NAME=admin
|
||||||
|
# - DB_AUTH=admin
|
||||||
|
# # ports:
|
||||||
|
# # - 8000:8000
|
||||||
|
# depends_on:
|
||||||
|
# - test-pena-auth-db
|
||||||
|
# # - pena-auth-migration
|
||||||
|
# networks:
|
||||||
|
# - penatest
|
||||||
|
#
|
||||||
|
# test-pena-auth-db:
|
||||||
|
# container_name: test-pena-auth-db
|
||||||
|
# init: true
|
||||||
|
# image: "mongo:6.0.3"
|
||||||
|
# command: mongod --quiet --logpath /dev/null
|
||||||
|
# volumes:
|
||||||
|
# - test-mongodb:/data/db
|
||||||
|
# - test-mongoconfdb:/data/configdb
|
||||||
|
# environment:
|
||||||
|
# MONGO_INITDB_ROOT_USERNAME: test
|
||||||
|
# MONGO_INITDB_ROOT_PASSWORD: test
|
||||||
|
# # ports:
|
||||||
|
# # - 27017:27017
|
||||||
|
# networks:
|
||||||
|
# - penatest
|
||||||
|
|
||||||
|
test-minio:
|
||||||
|
container_name: test-minio
|
||||||
|
init: true
|
||||||
|
image: quay.io/minio/minio
|
||||||
|
volumes:
|
||||||
|
- test-minio:/data
|
||||||
|
command: [ "minio", "--quiet", "server", "/data" ]
|
||||||
|
networks:
|
||||||
|
- penatest
|
||||||
|
|
||||||
|
test-squiz:
|
||||||
|
container_name: test-squiz
|
||||||
|
init: true
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: TestsDockerfile
|
||||||
|
depends_on:
|
||||||
|
test-postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
# test-pena-auth-service:
|
||||||
|
# condition: service_healthy
|
||||||
|
# volumes:
|
||||||
|
# - ./../..:/app:ro
|
||||||
|
# command: [ "go", "test", "./tests", "-run", "TestFoo" ]
|
||||||
|
command: [ "go", "test", "-parallel", "1", "./tests" ]
|
||||||
|
networks:
|
||||||
|
- penatest
|
||||||
|
|
||||||
|
networks:
|
||||||
|
penatest:
|
||||||
|
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
test-minio:
|
||||||
|
test-postgres:
|
||||||
|
test-mongodb:
|
||||||
|
test-mongoconfdb:
|
58
go.mod
Normal file
58
go.mod
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
module amocrm
|
||||||
|
|
||||||
|
go 1.21.6
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/caarlos0/env/v8 v8.0.0
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.4
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/lib/pq v1.10.9
|
||||||
|
github.com/stretchr/testify v1.8.4
|
||||||
|
github.com/twmb/franz-go v1.16.1
|
||||||
|
go.uber.org/zap v1.27.0
|
||||||
|
google.golang.org/protobuf v1.33.0
|
||||||
|
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240710173639-ae1b5abeb71f
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/ClickHouse/clickhouse-go v1.5.4 // indirect
|
||||||
|
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.6 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.6 // 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.69 // indirect
|
||||||
|
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.19 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/rs/xid v1.5.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.7.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
|
||||||
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
|
golang.org/x/crypto v0.20.0 // indirect
|
||||||
|
golang.org/x/net v0.21.0 // indirect
|
||||||
|
golang.org/x/sys v0.17.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240223054633-6cb3d5ce45b6 // indirect
|
||||||
|
)
|
150
go.sum
Normal file
150
go.sum
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/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-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/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM=
|
||||||
|
github.com/gofiber/fiber/v2 v2.52.4/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/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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
|
||||||
|
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.6/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.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=
|
||||||
|
github.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ=
|
||||||
|
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||||
|
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
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.19 h1:tYLzDnjDXh9qIxSTKHwXwOYmm9d887Y7Y1ZkyXYHAN4=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.19/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
|
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.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||||
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
|
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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
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.16.1 h1:rpWc7fB9jd7TgmCyfxzenBI+QbgS8ZfJOUQE+tzPtbE=
|
||||||
|
github.com/twmb/franz-go v1.16.1/go.mod h1:/pER254UPPGp/4WfGqRi+SIRGE50RSQzVubQp6+N4FA=
|
||||||
|
github.com/twmb/franz-go/pkg/kmsg v1.7.0 h1:a457IbvezYfA5UkiBvyV3zj0Is3y1i8EJgqjJYoij2E=
|
||||||
|
github.com/twmb/franz-go/pkg/kmsg v1.7.0/go.mod h1:se9Mjdt0Nwzc9lnjJ0HyDtLyBnaBDAd7pCje47OhSyw=
|
||||||
|
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=
|
||||||
|
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.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
|
||||||
|
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
|
||||||
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
|
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.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||||
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/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.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-20240223054633-6cb3d5ce45b6 h1:oV+/HNX+JPoQ3/GUx08hio7d45WpY0AMGrFs7j70QlA=
|
||||||
|
penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240223054633-6cb3d5ce45b6/go.mod h1:lTmpjry+8evVkXWbEC+WMOELcFkRD1lFMc7J09mOndM=
|
||||||
|
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240628071842-da12f589207e h1:9wh9ch9UaJcC/b/SCgDWdj7UX1mPK7ko1PBNp5PpH5U=
|
||||||
|
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240628071842-da12f589207e/go.mod h1:nfZkoj8MCYaoP+xiPeUn5D0lIzinUr1qDkNfX0ng9rk=
|
||||||
|
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240628183520-89234a64c7fe h1:KRz7Blk/yniyY1iC5omxS8yZPb/uBEm0HhM6HGhs6Rw=
|
||||||
|
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240628183520-89234a64c7fe/go.mod h1:nfZkoj8MCYaoP+xiPeUn5D0lIzinUr1qDkNfX0ng9rk=
|
||||||
|
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240710173639-ae1b5abeb71f h1:AsazJV1Z1eCCKSTylddZnRp8ziy2YZofv7/AyPqqtXM=
|
||||||
|
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240710173639-ae1b5abeb71f/go.mod h1:nfZkoj8MCYaoP+xiPeUn5D0lIzinUr1qDkNfX0ng9rk=
|
181
internal/app/app.go
Normal file
181
internal/app/app.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/brokers"
|
||||||
|
"amocrm/internal/controllers"
|
||||||
|
"amocrm/internal/initialize"
|
||||||
|
"amocrm/internal/repository"
|
||||||
|
"amocrm/internal/server/http"
|
||||||
|
"amocrm/internal/service"
|
||||||
|
"amocrm/internal/tools"
|
||||||
|
"amocrm/internal/workers/data_updater"
|
||||||
|
"amocrm/internal/workers/limiter"
|
||||||
|
"amocrm/internal/workers/post_deals_worker"
|
||||||
|
"amocrm/internal/workers/post_fields_worker"
|
||||||
|
"amocrm/internal/workers/queueUpdater"
|
||||||
|
"amocrm/internal/workers_methods"
|
||||||
|
"amocrm/pkg/amoClient"
|
||||||
|
"amocrm/pkg/closer"
|
||||||
|
pena_social_auth "amocrm/pkg/pena-social-auth"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"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()
|
||||||
|
|
||||||
|
redisClient, err := initialize.Redis(ctx, config)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error init redis client", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
|
||||||
|
amoRepo, err := dal.NewAmoDal(ctx, config.PostgresCredentials)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error init amo repo in common repo", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
socialAithClient := pena_social_auth.NewClient(pena_social_auth.Deps{
|
||||||
|
PenaSocialAuthURL: config.PenaSocialAuthURL,
|
||||||
|
Logger: logger,
|
||||||
|
ReturnURL: config.ReturnURL,
|
||||||
|
})
|
||||||
|
|
||||||
|
rateLimiter := limiter.NewRateLimiter(ctx, 6, 1500*time.Millisecond)
|
||||||
|
|
||||||
|
amoClient := amoClient.NewAmoClient(amoClient.AmoDeps{
|
||||||
|
Logger: logger,
|
||||||
|
RedirectionURL: config.ReturnURL,
|
||||||
|
IntegrationID: config.IntegrationID,
|
||||||
|
IntegrationSecret: config.IntegrationSecret,
|
||||||
|
RateLimiter: rateLimiter,
|
||||||
|
})
|
||||||
|
|
||||||
|
redisRepo := repository.NewRepository(repository.Deps{
|
||||||
|
RedisClient: redisClient,
|
||||||
|
Logger: logger,
|
||||||
|
})
|
||||||
|
|
||||||
|
svc := service.NewService(service.Deps{
|
||||||
|
Repository: amoRepo,
|
||||||
|
Logger: logger,
|
||||||
|
SocialAuthClient: socialAithClient,
|
||||||
|
AmoClient: amoClient,
|
||||||
|
Producer: producer,
|
||||||
|
})
|
||||||
|
|
||||||
|
cntrlDeps := controllers.Deps{
|
||||||
|
Service: svc,
|
||||||
|
Logger: logger,
|
||||||
|
Verify: tools.NewVerify(config.IntegrationSecret, config.IntegrationID),
|
||||||
|
RedirectURL: config.RedirectURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
controller := controllers.NewController(cntrlDeps)
|
||||||
|
|
||||||
|
webhookController := controllers.NewWebhookController(cntrlDeps)
|
||||||
|
|
||||||
|
workerMethods := workers_methods.NewWorkersMethods(workers_methods.Deps{
|
||||||
|
Repo: amoRepo,
|
||||||
|
AmoClient: amoClient,
|
||||||
|
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.NewPostDealsWC(post_deals_worker.Deps{
|
||||||
|
AmoRepo: amoRepo,
|
||||||
|
AmoClient: amoClient,
|
||||||
|
RedisRepo: redisRepo,
|
||||||
|
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.HTTPHost + ":" + config.HTTPPort); err != nil {
|
||||||
|
logger.Error("Server startup error", zap.Error(err))
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
server.ListRoutes()
|
||||||
|
|
||||||
|
shutdownGroup.Add(closer.CloserFunc(server.Shutdown))
|
||||||
|
shutdownGroup.Add(closer.CloserFunc(amoRepo.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 (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/twmb/franz-go/pkg/kgo"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 := "654a8909725f47e926f0bebc"
|
||||||
|
|
||||||
|
err := c.service.UpdateListCustom(ctx.Context(), accountID)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.SendStatus(fiber.StatusOK)
|
||||||
|
}
|
79
internal/controllers/initial.go
Normal file
79
internal/controllers/initial.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/service"
|
||||||
|
"amocrm/internal/tools"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
Service *service.Service
|
||||||
|
Logger *zap.Logger
|
||||||
|
Verify *tools.Verify
|
||||||
|
RedirectURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
service *service.Service
|
||||||
|
logger *zap.Logger
|
||||||
|
verify *tools.Verify
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(deps Deps) *Controller {
|
||||||
|
return &Controller{
|
||||||
|
service: deps.Service,
|
||||||
|
logger: deps.Logger,
|
||||||
|
verify: deps.Verify,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Name() string {
|
||||||
|
return "amocrm"
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebhookController struct {
|
||||||
|
service *service.Service
|
||||||
|
logger *zap.Logger
|
||||||
|
verify *tools.Verify
|
||||||
|
redirectURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWebhookController(deps Deps) *WebhookController {
|
||||||
|
return &WebhookController{
|
||||||
|
service: deps.Service,
|
||||||
|
logger: deps.Logger,
|
||||||
|
verify: deps.Verify,
|
||||||
|
redirectURL: deps.RedirectURL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WebhookController) Register(router fiber.Router) {
|
||||||
|
//todo поменять как было GET webhook/create
|
||||||
|
router.Get("/create", c.WebhookCreate)
|
||||||
|
//todo поменять как было webhook/delete
|
||||||
|
router.Delete("/delete", c.WebhookDelete)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WebhookController) Name() string {
|
||||||
|
return "webhook"
|
||||||
|
}
|
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 := "654a8909725f47e926f0bebc"
|
||||||
|
|
||||||
|
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.RulesReq
|
||||||
|
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.RulesReq
|
||||||
|
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)
|
||||||
|
}
|
58
internal/controllers/steps.go
Normal file
58
internal/controllers/steps.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
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"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
pipelineIDStr := ctx.Query("pipelineID")
|
||||||
|
if pipelineIDStr == "" {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).SendString("pipeline id is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
pipelineID, err := strconv.Atoi(pipelineIDStr)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).SendString("invalid pipeline id parameter")
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := extractParams(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := c.service.GetStepsWithPagination(ctx.Context(), req, accountID, pipelineID)
|
||||||
|
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 := "654a8909725f47e926f0bebc"
|
||||||
|
|
||||||
|
err := c.service.UpdateListSteps(ctx.Context(), accountID)
|
||||||
|
if err != nil {
|
||||||
|
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.SendStatus(fiber.StatusOK)
|
||||||
|
}
|
47
internal/controllers/tags.go
Normal file
47
internal/controllers/tags.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) 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)
|
||||||
|
}
|
93
internal/controllers/user.go
Normal file
93
internal/controllers/user.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
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 {
|
||||||
|
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)
|
||||||
|
}
|
87
internal/controllers/webhook.go
Normal file
87
internal/controllers/webhook.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/service"
|
||||||
|
"amocrm/internal/tools"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// контроллер на который редиректятся ответы по авторизации в амо
|
||||||
|
func (c *WebhookController) WebhookCreate(ctx *fiber.Ctx) error {
|
||||||
|
code := ctx.Query("code") // Authorization 20 минут
|
||||||
|
referer := ctx.Query("referer") // адрес аккаунта пользователя
|
||||||
|
state := ctx.Query("state") // строка которая передавалась в соц аус сервисе
|
||||||
|
fromWidget := ctx.Query("from_widget")
|
||||||
|
platform := ctx.Query("platform") // ru/global 1/2
|
||||||
|
noAccess := ctx.Query("error")
|
||||||
|
|
||||||
|
if noAccess != "" {
|
||||||
|
return ctx.Status(http.StatusForbidden).SendString("Access denied")
|
||||||
|
}
|
||||||
|
|
||||||
|
accountID, _, err := tools.DeserializeProtobufMessage(state)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("error Deserialize Protobuf Message", zap.Error(err))
|
||||||
|
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if accountID == "" || code == "" || referer == "" {
|
||||||
|
c.logger.Error("error required fields do not be nil", zap.Error(err))
|
||||||
|
return ctx.Status(fiber.StatusBadRequest).SendString("nil required fields")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := service.ParamsWebhookCreate{
|
||||||
|
Code: code,
|
||||||
|
Referer: referer,
|
||||||
|
AccountID: accountID,
|
||||||
|
FromWidget: fromWidget,
|
||||||
|
Platform: platform,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.service.WebhookCreate(ctx.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("error create 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)
|
||||||
|
}
|
41
internal/initialize/config.go
Normal file
41
internal/initialize/config.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package initialize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caarlos0/env/v8"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
AppName string `env:"APP_NAME" envDefault:"amocrm"`
|
||||||
|
HTTPHost string `env:"HTTP_HOST" envDefault:"localhost"`
|
||||||
|
HTTPPort string `env:"HTTP_PORT" envDefault:"8001"`
|
||||||
|
PostgresCredentials string `env:"PG_CRED" envDefault:"host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"`
|
||||||
|
KafkaBrokers string `env:"KAFKA_BROKERS" envDefault:"localhost:9092"`
|
||||||
|
KafkaTopic string `env:"KAFKA_TOPIC" envDefault:"test-topic"`
|
||||||
|
KafkaGroup string `env:"KAFKA_GROUP" envDefault:"amoCRM"`
|
||||||
|
RedisAddr string `env:"REDIS_ADDR" envDefault:"localhost:6379"`
|
||||||
|
RedisPassword string `env:"REDIS_PASS" envDefault:"admin"`
|
||||||
|
RedisDB int `env:"REDIS_DB" envDefault:"2"`
|
||||||
|
// урл в соц аус сервисе для генерации ссылки для авторизации в амо
|
||||||
|
PenaSocialAuthURL string `env:"PENA_SOCIAL_AUTH_URL" envDefault:"http://localhost:8000/amocrm/auth"`
|
||||||
|
// урл на который будет возвращен пользователь после авторизации это webhook/create get
|
||||||
|
ReturnURL string `env:"RETURN_URL" envDefault:"https://squiz.pena.digital/squiz/amocrm/oauth"`
|
||||||
|
// id интеграции
|
||||||
|
IntegrationID string `env:"INTEGRATION_ID" envDefault:"2dbd6329-9be6-41f2-aa5f-964b9e723e49"`
|
||||||
|
// секрет интеграции
|
||||||
|
IntegrationSecret string `env:"INTEGRATION_SECRET" envDefault:"tNK3LwL4ovP0OBK4jKDHJ3646PqRJDOKQYgY6P2t6DCuV8LEzDzszTDY0Fhwmzc8"`
|
||||||
|
//AmoStorageURL string `env:"AMO_STORAGE_URL" envDefault:"https://drive-b.amocrm.ru"`
|
||||||
|
RedirectURL string `env:"REDIRECT_URL" envDefault:"https://squiz.pena.digital/integrations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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.KafkaTopic),
|
||||||
|
kgo.ConsumeTopics(config.KafkaTopic),
|
||||||
|
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.RedisAddr,
|
||||||
|
Password: cfg.RedisPassword,
|
||||||
|
DB: cfg.RedisDB,
|
||||||
|
})
|
||||||
|
|
||||||
|
status := rdb.Ping(ctx)
|
||||||
|
if err := status.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rdb, nil
|
||||||
|
}
|
72
internal/models/createContact.go
Normal file
72
internal/models/createContact.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type CreateContactReq struct {
|
||||||
|
Name string `json:"name"` // Название контакта
|
||||||
|
FirstName string `json:"first_name"` // Имя контакта
|
||||||
|
LastName string `json:"last_name"` // Фамилия контакта
|
||||||
|
ResponsibleUserID int32 `json:"responsible_user_id"` // ID пользователя, ответственного за контакт
|
||||||
|
CreatedBy int64 `json:"created_by"` // ID пользователя, создавший контакт
|
||||||
|
UpdatedBy int64 `json:"updated_by"` // ID пользователя, изменивший контакт
|
||||||
|
CreatedAt int64 `json:"created_at"` // Дата создания контакта, передается в Unix Timestamp
|
||||||
|
UpdatedAt int64 `json:"updated_at"` // Дата изменения контакта, передается в Unix Timestamp
|
||||||
|
CustomFieldsValues []FieldsValues `json:"custom_fields_values"`
|
||||||
|
TagsToAdd []Tag `json:"tags_to_add"`
|
||||||
|
Embed Embedd `json:"_embedded"`
|
||||||
|
RequestID string `json:"request_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContactResponse struct {
|
||||||
|
Links struct {
|
||||||
|
Self struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
} `json:"self"`
|
||||||
|
} `json:"_links"`
|
||||||
|
Embedded struct {
|
||||||
|
Contacts []struct {
|
||||||
|
ID int32 `json:"id"`
|
||||||
|
RequestID string `json:"request_id"`
|
||||||
|
Links struct {
|
||||||
|
Self struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
} `json:"self"`
|
||||||
|
} `json:"_links"`
|
||||||
|
} `json:"contacts"`
|
||||||
|
} `json:"_embedded"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LinkedContactReq struct {
|
||||||
|
EntityID int32 `json:"entity_id"` // ID главной сущности
|
||||||
|
ToEntityID int32 `json:"to_entity_id"` // ID связанной сущности
|
||||||
|
ToEntityType string `json:"to_entity_type"` // Тип связанной сущности (leads, contacts, companies, customers, catalog_elements)
|
||||||
|
Metadata struct {
|
||||||
|
//CatalogID int `json:"catalog_id"` // ID каталога
|
||||||
|
//Quantity int `json:"quantity"` // Количество прикрепленных элементов каталогов
|
||||||
|
IsMain bool `json:"is_main"` // Является ли контакт главным
|
||||||
|
//UpdatedBy int `json:"updated_by"` // ID пользователя, от имени которого осуществляется прикрепление
|
||||||
|
//PriceID int `json:"price_id"` // ID поля типа Цена, которое будет установлено для привязанного элемента в контексте сущности
|
||||||
|
} `json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LinkedContactResponse struct {
|
||||||
|
TotalItems int `json:"_total_items"`
|
||||||
|
Links struct {
|
||||||
|
Self struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
} `json:"self"`
|
||||||
|
} `json:"_links"`
|
||||||
|
Embedded struct {
|
||||||
|
Links []struct {
|
||||||
|
EntityID int `json:"entity_id"`
|
||||||
|
EntityType string `json:"entity_type"`
|
||||||
|
ToEntityID int `json:"to_entity_id"`
|
||||||
|
ToEntityType string `json:"to_entity_type"`
|
||||||
|
Metadata struct {
|
||||||
|
Quantity int `json:"quantity"`
|
||||||
|
CatalogID int `json:"catalog_id"`
|
||||||
|
IsMain bool `json:"is_main"`
|
||||||
|
UpdatedBy int `json:"updated_by"`
|
||||||
|
PriceID int `json:"price_id"`
|
||||||
|
} `json:"metadata"`
|
||||||
|
} `json:"links"`
|
||||||
|
} `json:"_embedded"`
|
||||||
|
}
|
226
internal/models/createDeal.go
Normal file
226
internal/models/createDeal.go
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DealReq struct {
|
||||||
|
Name string `json:"name"` // название сделки
|
||||||
|
Price int `json:"price"` // бюджет сделки
|
||||||
|
StatusID int32 `json:"status_id"` // id статуса (шага в нашем случае) в который добавляется сделка
|
||||||
|
PipelineID int32 `json:"pipeline_id"` // ID воронки, в которую добавляется сделка
|
||||||
|
CreatedBy int32 `json:"created_by"` // id пользователя amoid который создает сделку (тот кто подключил интеграцию)
|
||||||
|
UpdatedBy int `json:"updated_by"` // ID пользователя, изменяющий сделку. При передаче значения 0, сделка будет считаться измененной роботом
|
||||||
|
ClosedAt int64 `json:"closed_at"` // Дата закрытия сделки, передается в Unix Timestamp
|
||||||
|
CreatedAt int64 `json:"created_at"` // Дата создания сделки, передается в Unix Timestamp
|
||||||
|
UpdatedAt int64 `json:"updated_at"` // Дата изменения сделки, передается в Unix Timestamp
|
||||||
|
LossReasonID *int `json:"loss_reason_id,omitempty"` // ID причины отказа
|
||||||
|
ResponsibleUserID int32 `json:"responsible_user_id"` // ID пользователя, ответственного за сделку, в нашем случае PerformerID
|
||||||
|
CustomFieldsValues []FieldsValues `json:"custom_fields_values"` // Массив полей которые заполняются значениями
|
||||||
|
TagsToAdd []Tag `json:"tags_to_add"` // Массив тегов для добавления
|
||||||
|
Embed Embedd `json:"_embedded"`
|
||||||
|
RequestID string `json:"request_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValueInterface interface{}
|
||||||
|
|
||||||
|
type FieldsValues struct {
|
||||||
|
FieldID int `json:"field_id"`
|
||||||
|
Values []ValueInterface `json:"values"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Values struct {
|
||||||
|
Value string `json:"value"` // пока так пока не понятно
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValuesFile struct {
|
||||||
|
Value ValueFile `json:"value"`
|
||||||
|
}
|
||||||
|
type ValueFile struct {
|
||||||
|
FileUUID string `json:"file_uuid"`
|
||||||
|
VersionUUID string `json:"version_uuid"`
|
||||||
|
FileName string `json:"file_name"`
|
||||||
|
FileSize int64 `json:"file_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fv *FieldsValues) UnmarshalJSON(data []byte) error {
|
||||||
|
type Alias FieldsValues
|
||||||
|
aux := struct {
|
||||||
|
Alias
|
||||||
|
FieldID int `json:"field_id"`
|
||||||
|
Values []json.RawMessage `json:"values"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fv.FieldID = aux.FieldID
|
||||||
|
fv.Values = make([]ValueInterface, len(aux.Values))
|
||||||
|
|
||||||
|
for i, rawVal := range aux.Values {
|
||||||
|
var v map[string]interface{}
|
||||||
|
if err := json.Unmarshal(rawVal, &v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := v["value"]; !ok {
|
||||||
|
return fmt.Errorf("missing value in JSON")
|
||||||
|
}
|
||||||
|
|
||||||
|
var value ValueInterface
|
||||||
|
if _, ok := v["value"].(map[string]interface{}); ok {
|
||||||
|
var fileStruct ValuesFile
|
||||||
|
if err := json.Unmarshal(rawVal, &fileStruct); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
value = fileStruct
|
||||||
|
} else {
|
||||||
|
var valValue Values
|
||||||
|
if err := json.Unmarshal(rawVal, &valValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
value = valValue
|
||||||
|
}
|
||||||
|
|
||||||
|
fv.Values[i] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Embedd struct {
|
||||||
|
Tags []Tag `json:"tags"` // Данные тегов, добавляемых к сделке
|
||||||
|
Contact []Contact `json:"contacts"` // Данные контактов, которые будет прикреплены к сделке
|
||||||
|
Company []Company `json:"companies"` // Данные компании, которая будет прикреплена к сделке
|
||||||
|
Source Source `json:"source"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Contact struct {
|
||||||
|
ID int32 `json:"id"`
|
||||||
|
Name string `json:"first_name"`
|
||||||
|
ResponsibleUserID int32 `json:"responsible_user_id"` // ID пользователя, ответственного за сделку, в нашем случае PerformerID
|
||||||
|
CreatedBy int32 `json:"created_by"` // id пользователя amoid который создает сделку (тот кто подключил интеграцию)
|
||||||
|
UpdatedBy int `json:"updated_by"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"` // Дата изменения сделки, передается в Unix Timestamp
|
||||||
|
CustomFieldsValues []FieldsValues `json:"custom_fields_values"` // Массив полей которые заполняются значениями
|
||||||
|
}
|
||||||
|
|
||||||
|
type Company struct {
|
||||||
|
Name string `json:"name"` // Название компании
|
||||||
|
ResponsibleUserID int32 `json:"responsible_user_id"` // ID пользователя, ответственного за сделку, в нашем случае PerformerID
|
||||||
|
CreatedBy int32 `json:"created_by"` // id пользователя amoid который создает сделку (тот кто подключил интеграцию)
|
||||||
|
UpdatedBy int `json:"updated_by"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"` // Дата изменения сделки, передается в Unix Timestamp
|
||||||
|
CustomFieldsValues []FieldsValues `json:"custom_fields_values"` // Массив полей которые заполняются значениями
|
||||||
|
}
|
||||||
|
|
||||||
|
type Source struct {
|
||||||
|
ExternalID int `json:"external_id"` // Внешний ID источника
|
||||||
|
Type string `json:"type"` // Тип источника. Для сделок, добавляемых интеграциями, поддерживается только widget
|
||||||
|
}
|
||||||
|
|
||||||
|
type DealResp struct {
|
||||||
|
DealID int32 `json:"id"` // ID сделки
|
||||||
|
ContactID int `json:"contact_id"` // ID контакта
|
||||||
|
CompanyID int `json:"company_id"` // ID компании
|
||||||
|
Merged bool `json:"merged"` // Флаг, который показывает, найден дубль подходящий под условия поиска дублей и произведено объединение или нет
|
||||||
|
RequestID []string `json:"request_id"` // Массив строк с пользовательскими идентификаторами, которые были переданы с каждой сущностью
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateDealReq struct {
|
||||||
|
DealID int32 `json:"id"` // ID сделки
|
||||||
|
CustomFieldsValues []FieldsValues `json:"custom_fields_values"` // Массив полей которые заполняются значениями
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateDealResp struct {
|
||||||
|
Embedded EmbeddedUpdateDeal `json:"_embedded"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmbeddedUpdateDeal struct {
|
||||||
|
Leads []struct {
|
||||||
|
ID int32 `json:"id"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Customer struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
NextPrice int `json:"next_price"`
|
||||||
|
NextDate int64 `json:"next_date"`
|
||||||
|
ResponsibleUserID int32 `json:"responsible_user_id"`
|
||||||
|
StatusID *int32 `json:"status_id,omitempty"`
|
||||||
|
Periodicity int `json:"periodicity"`
|
||||||
|
CreatedBy int `json:"created_by"`
|
||||||
|
UpdatedBy int `json:"updated_by"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
CustomFields []FieldsValues `json:"custom_fields_values"`
|
||||||
|
TagsToAdd []Tag `json:"tags_to_add"`
|
||||||
|
Embed Embedd `json:"_embedded"`
|
||||||
|
RequestID string `json:"request_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomerResp struct {
|
||||||
|
Embedded EmbeddedCreateCustomers `json:"_embedded"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmbeddedCreateCustomers struct {
|
||||||
|
Customers []struct {
|
||||||
|
ID int32 `json:"id"`
|
||||||
|
RequestID string `json:"request_id"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateSession struct {
|
||||||
|
FileName string `json:"file_name"` // обязательное поле
|
||||||
|
FileSize int64 `json:"file_size"` // обязательное поле
|
||||||
|
FileUUID string `json:"file_uuid"` // UUID файла, для которого загружается новая версия файла. Если UUID не задан, то будет создан новый файл.
|
||||||
|
ContentType string `json:"content_type"` // MIME-тип файла
|
||||||
|
WithPreview bool `json:"with_preview"` // При установке данного флага для файла будет сгенерировано превью
|
||||||
|
}
|
||||||
|
|
||||||
|
// представляет данные о созданной сессии загрузки файла
|
||||||
|
type UploadSession struct {
|
||||||
|
SessionID int `json:"session_id"`
|
||||||
|
UploadURL string `json:"upload_url"`
|
||||||
|
MaxFileSize int64 `json:"max_file_size"`
|
||||||
|
MaxPartSize int64 `json:"max_part_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// представляет информацию о загруженном файле
|
||||||
|
type UploadedFile struct {
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
IsTrashed bool `json:"is_trashed"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
SanitizedName string `json:"sanitized_name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
SourceID int `json:"source_id"`
|
||||||
|
VersionUUID string `json:"version_uuid"`
|
||||||
|
HasMultipleVersions bool `json:"has_multiple_versions"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
CreatedBy struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
} `json:"created_by"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
DeletedAt int64 `json:"deleted_at"`
|
||||||
|
DeletedBy interface{} `json:"deleted_by"`
|
||||||
|
Metadata Metadata `json:"metadata"`
|
||||||
|
Previews []PreviewFile `json:"previews"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Metadata struct {
|
||||||
|
Extension string `json:"extension"`
|
||||||
|
MIMEType string `json:"mime_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PreviewFile struct {
|
||||||
|
DownloadLink string `json:"download_link"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
}
|
72
internal/models/createWebHook.go
Normal file
72
internal/models/createWebHook.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
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"` // Полученный код авторизации
|
||||||
|
RedirectUrl string `json:"redirect_uri"` // Redirect URI указанный в настройках интеграции. Должен четко совпадать с тем, что указан в настройках
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateWebHookResp struct {
|
||||||
|
TokenType string `json:"token_type"` // Тип токена
|
||||||
|
ExpiresIn int64 `json:"expires_in"` // ttl в секундах
|
||||||
|
AccessToken string `json:"access_token"` // Access Token в формате JWT
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateWebHookReq struct {
|
||||||
|
ClientID string `json:"client_id"` // id интеграции
|
||||||
|
ClientSecret string `json:"client_secret"` // Секрет интеграции
|
||||||
|
GrantType string `json:"grant_type"` // Тип авторизационных данных (для кода авторизации – authorization_code)
|
||||||
|
RefreshToken string `json:"refresh_token"` // Refresh токен
|
||||||
|
RedirectUrl string `json:"redirect_uri"` // Redirect URI указанный в настройках интеграции. Должен четко совпадать с тем, что указан в настройках
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebHookRequest interface {
|
||||||
|
SetClientID(str string)
|
||||||
|
SetClientSecret(str string)
|
||||||
|
GetGrantType() string
|
||||||
|
SetRedirectURL(str 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) SetRedirectURL(str string) {
|
||||||
|
req.RedirectUrl = str
|
||||||
|
}
|
||||||
|
|
||||||
|
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) SetRedirectURL(str string) {
|
||||||
|
req.RedirectUrl = str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *UpdateWebHookReq) GetToken() string {
|
||||||
|
return req.RefreshToken
|
||||||
|
}
|
20
internal/models/forRedis.go
Normal file
20
internal/models/forRedis.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type SaveDeal struct {
|
||||||
|
AnswerID int64
|
||||||
|
DealID int32
|
||||||
|
AccessToken string
|
||||||
|
SubDomain string
|
||||||
|
}
|
||||||
|
|
||||||
|
type MappingDealsData struct {
|
||||||
|
AnswerID int64
|
||||||
|
DealID int32
|
||||||
|
LeadFields []FieldsValues
|
||||||
|
SubDomain string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForRestoringData struct {
|
||||||
|
SaveDeal SaveDeal
|
||||||
|
LeadFields []FieldsValues
|
||||||
|
}
|
92
internal/models/getListFields.go
Normal file
92
internal/models/getListFields.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetListFieldsReq struct {
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
//Filter []string `json:"filter"` // пока не понял что это может быть
|
||||||
|
EntityType model.EntityType `json:"entityType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseGetListFields struct {
|
||||||
|
TotalItems int `json:"_total_items"`
|
||||||
|
Page int `json:"_page"`
|
||||||
|
PageCount int `json:"_page_count"`
|
||||||
|
Links Links `json:"_links"`
|
||||||
|
Embedded EmbeddedFields `json:"_embedded"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Links struct {
|
||||||
|
Self SelfLink `json:"self"`
|
||||||
|
Next SelfLink `json:"next"`
|
||||||
|
Last SelfLink `json:"last"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmbeddedFields struct {
|
||||||
|
CustomFields []CustomField `json:"custom_fields"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomField struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Sort int `json:"sort"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Entity_type string `json:"entity_type"`
|
||||||
|
IsComputed bool `json:"is_computed"`
|
||||||
|
IsPredefined bool `json:"is_predefined"`
|
||||||
|
IsDeletable bool `json:"is_deletable"`
|
||||||
|
IsVisible bool `json:"is_visible"`
|
||||||
|
IsRequired bool `json:"is_required"`
|
||||||
|
Settings []interface{} `json:"settings,omitempty"` // проверить что это (array null)
|
||||||
|
Remind *string `json:"remind,omitempty"`
|
||||||
|
Currency *string `json:"currency,omitempty"`
|
||||||
|
Enums []Enum `json:"enums,omitempty"`
|
||||||
|
Nested []Nested `json:"nested,omitempty"`
|
||||||
|
IsAPIOnly bool `json:"is_api_only"`
|
||||||
|
GroupID *string `json:"group_id,omitempty"`
|
||||||
|
RequiredStatuses []RequiredStatus `json:"required_statuses,omitempty"`
|
||||||
|
HiddenStatuses []HiddenStatus `json:"hidden_statuses,omitempty"`
|
||||||
|
ChainedLists []ChainedList `json:"chained_lists,omitempty"`
|
||||||
|
TrackingCallback string `json:"tracking_callback,omitempty"`
|
||||||
|
SearchIn *string `json:"search_in,omitempty"`
|
||||||
|
Links SelfLink `json:"_links"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Enum struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
Sort int `json:"sort"`
|
||||||
|
Code *string `json:"code,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Nested struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
ParentID int `json:"parent_id"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
Sort int `json:"sort"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequiredStatus struct {
|
||||||
|
StatusID int `json:"status_id"`
|
||||||
|
PipelineID int `json:"pipeline_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HiddenStatus struct {
|
||||||
|
StatusID int `json:"status_id"`
|
||||||
|
PipelineID int `json:"pipeline_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChainedList struct {
|
||||||
|
Title *string `json:"title,omitempty"`
|
||||||
|
CatalogID int `json:"catalog_id"`
|
||||||
|
ParentCatalogID int `json:"parent_catalog_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddLeadsFields struct {
|
||||||
|
Type model.FieldType `json:"type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
47
internal/models/getListPipelines.go
Normal file
47
internal/models/getListPipelines.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type PipelineResponse struct {
|
||||||
|
TotalItems int `json:"_total_items"`
|
||||||
|
Links struct {
|
||||||
|
Self struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
} `json:"self"`
|
||||||
|
} `json:"_links"`
|
||||||
|
Embedded struct {
|
||||||
|
Pipelines []Pipeline `json:"pipelines"`
|
||||||
|
} `json:"_embedded"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pipeline struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Sort int `json:"sort"`
|
||||||
|
IsMain bool `json:"is_main"`
|
||||||
|
IsUnsortedOn bool `json:"is_unsorted_on"`
|
||||||
|
IsArchive bool `json:"is_archive"`
|
||||||
|
AccountID int `json:"account_id"`
|
||||||
|
Links struct {
|
||||||
|
Self struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
} `json:"self"`
|
||||||
|
} `json:"_links"`
|
||||||
|
Embedded struct {
|
||||||
|
Statuses []Status `json:"statuses"`
|
||||||
|
} `json:"_embedded"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Status struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Sort int `json:"sort"`
|
||||||
|
IsEditable bool `json:"is_editable"`
|
||||||
|
PipelineID int `json:"pipeline_id"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
AccountID int `json:"account_id"`
|
||||||
|
Links struct {
|
||||||
|
Self struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
} `json:"self"`
|
||||||
|
} `json:"_links"`
|
||||||
|
}
|
36
internal/models/getListSteps.go
Normal file
36
internal/models/getListSteps.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type ResponseGetListSteps struct {
|
||||||
|
TotalItems int `json:"_total_items"`
|
||||||
|
Embedded EmbeddedSteps `json:"_embedded"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmbeddedSteps struct {
|
||||||
|
Statuses []Statuses `json:"statuses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Statuses struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Sort int `json:"sort"`
|
||||||
|
IsEditable bool `json:"is_editable"`
|
||||||
|
PipelineID int `json:"pipeline_id"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
AccountID int `json:"account_id"`
|
||||||
|
Links LinksSelf `json:"_links"`
|
||||||
|
Descriptions []Descriptions `json:"descriptions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Descriptions struct {
|
||||||
|
AccountID int `json:"account_id"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
CreatedBy int `json:"created_by"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ID int `json:"id"`
|
||||||
|
Level string `json:"level"`
|
||||||
|
PipelineID int `json:"pipeline_id"`
|
||||||
|
StatusID int `json:"status_id"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
UpdatedBy int `json:"updated_by"`
|
||||||
|
}
|
34
internal/models/getListTags.go
Normal file
34
internal/models/getListTags.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetListTagsReq struct {
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
Filter Filter `json:"filter"`
|
||||||
|
EntityType model.EntityType `json:"entityType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Filter struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
ID []int `json:"id"`
|
||||||
|
Query string `json:"query"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseGetListTags struct {
|
||||||
|
Page int `json:"_page"`
|
||||||
|
Links Links `json:"_links"`
|
||||||
|
Embedded EmbeddedTags `json:"_embedded"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmbeddedTags struct {
|
||||||
|
Tags []Tag `json:"tags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tag struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Color *string `json:"color,omitempty"`
|
||||||
|
}
|
116
internal/models/getUserList.go
Normal file
116
internal/models/getUserList.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type RequestGetListUsers struct {
|
||||||
|
Page int
|
||||||
|
Limit int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseGetListUsers struct {
|
||||||
|
TotalItems int `json:"_total_items"`
|
||||||
|
Links LinksSelf `json:"_links"`
|
||||||
|
Embedded EmbeddedGetListUsers `json:"_embedded"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmbeddedGetListUsers struct {
|
||||||
|
Users []Users `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SelfLink struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LinksSelf struct {
|
||||||
|
Self SelfLink `json:"self"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Users struct {
|
||||||
|
ID int32 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
IsConfirmed bool `json:"is_confirmed"`
|
||||||
|
ConfirmLinkSentAt int `json:"confirm_link_sent_at"`
|
||||||
|
Lang string `json:"lang"`
|
||||||
|
FullName string `json:"full_name"`
|
||||||
|
Groups []Groups `json:"groups"`
|
||||||
|
Links SelfLink `json:"_links"`
|
||||||
|
Rights Rights `json:"_embedded"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rights struct {
|
||||||
|
Leads `json:"leads"`
|
||||||
|
Contacts `json:"contacts"`
|
||||||
|
Companies `json:"companies"`
|
||||||
|
Tasks `json:"tasks"`
|
||||||
|
MailAccess bool `json:"mail_access"`
|
||||||
|
CatalogAccess bool `json:"catalog_access"`
|
||||||
|
StatusRights []StatusRights `json:"status_rights"`
|
||||||
|
IsAdmin bool `json:"is_admin"`
|
||||||
|
IsFree bool `json:"is_free"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
GroupID int `json:"group_id,omitempty"`
|
||||||
|
RoleID int `json:"role_id,omitempty"`
|
||||||
|
Role string `json:"role,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Leads struct {
|
||||||
|
View string `json:"view"`
|
||||||
|
Edit string `json:"edit"`
|
||||||
|
Add string `json:"add"`
|
||||||
|
Delete string `json:"delete"`
|
||||||
|
Export string `json:"export"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Contacts struct {
|
||||||
|
View string `json:"view"`
|
||||||
|
Edit string `json:"edit"`
|
||||||
|
Add string `json:"add"`
|
||||||
|
Delete string `json:"delete"`
|
||||||
|
Export string `json:"export"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Companies struct {
|
||||||
|
View string `json:"view"`
|
||||||
|
Edit string `json:"edit"`
|
||||||
|
Add string `json:"add"`
|
||||||
|
Delete string `json:"delete"`
|
||||||
|
Export string `json:"export"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tasks struct {
|
||||||
|
Edit string `json:"edit"`
|
||||||
|
Delete string `json:"delete"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusRights struct {
|
||||||
|
EntityType string `json:"entity_type"`
|
||||||
|
PipelineID int `json:"pipeline_id"`
|
||||||
|
StatusID int `json:"status_id"`
|
||||||
|
Rights RightsGetListUsers `json:"rights"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RightsGetListUsers struct {
|
||||||
|
View string `json:"view"`
|
||||||
|
Edit string `json:"edit"`
|
||||||
|
Delete string `json:"delete"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Roles struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Links SelfLink `json:"_links"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Groups struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//type Embedded struct {
|
||||||
|
// Roles []Roles `json:"roles"`
|
||||||
|
// Groups []Groups `json:"groups"`
|
||||||
|
//}
|
||||||
|
|
||||||
|
type Embedded struct {
|
||||||
|
Rights Rights `json:"rights"`
|
||||||
|
}
|
32
internal/models/kafkaMess.go
Normal file
32
internal/models/kafkaMess.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||||
|
|
||||||
|
type KafkaMessage struct {
|
||||||
|
AccountID string
|
||||||
|
AuthCode string
|
||||||
|
RefererURL string
|
||||||
|
Type MessageType
|
||||||
|
Rule KafkaRule
|
||||||
|
}
|
||||||
|
|
||||||
|
type KafkaRule struct {
|
||||||
|
QuizID int32
|
||||||
|
PerformerID int32 // айдишник ответственного за сделку
|
||||||
|
PipelineID int32 // айдишник воронки
|
||||||
|
StepID int32 // айдишник этапа
|
||||||
|
Fieldsrule model.Fieldsrule
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
160
internal/models/userInfo.go
Normal file
160
internal/models/userInfo.go
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type AmocrmUserInformation struct {
|
||||||
|
//ID аккаунта
|
||||||
|
ID int32 `json:"id" bson:"id"`
|
||||||
|
//Название аккаунта
|
||||||
|
Name string `json:"name" bson:"name"`
|
||||||
|
//Субдомен аккаунта
|
||||||
|
Subdomain string `json:"subdomain" bson:"subdomain"`
|
||||||
|
//ID текущего пользователя
|
||||||
|
CurrentUserID int `json:"current_user_id" bson:"current_user_id"`
|
||||||
|
//Страна, указанная в настройках аккаунта
|
||||||
|
Country string `json:"country" bson:"country"`
|
||||||
|
//Режим покупателей. Возможные варианты: unavailable (функционал недоступен), disabled (функцонал отключен), segments (сегментация), dynamic (deprecated), periodicity (периодические покупки)
|
||||||
|
CustomersMode string `json:"customers_mode" bson:"customers_mode"`
|
||||||
|
//Включен ли функционал “Неразобранного” в аккаунте
|
||||||
|
IsUnsortedOn bool `json:"is_unsorted_on" bson:"is_unsorted_on"`
|
||||||
|
//Включен ли функционал причин отказа
|
||||||
|
IsLossReasonEnabled bool `json:"is_loss_reason_enabled" bson:"is_loss_reason_enabled"`
|
||||||
|
//Включен ли функционал Типовых вопросов (доступен только на профессиональном тарифе)
|
||||||
|
IsHelpbotEnabled bool `json:"is_helpbot_enabled" bson:"is_helpbot_enabled"`
|
||||||
|
//Является ли данный аккаунт техническим
|
||||||
|
IsTechnicalAccount bool `json:"is_technical_account" bson:"is_technical_account"`
|
||||||
|
//Порядок отображения имен контактов (1 – Имя, Фамилия; 2 – Фамилия, Имя)
|
||||||
|
ContactNameDisplayOrder int `json:"contact_name_display_order" bson:"contact_name_display_order"`
|
||||||
|
//Требуется GET параметр with. Уникальный идентификатор аккаунта для работы с сервисом чатов amoJo
|
||||||
|
AmojoID string `json:"amojo_id" bson:"amojo_id"`
|
||||||
|
//Требуется GET параметр with. Адрес сервиса файлов для конкретного аккаунта
|
||||||
|
DriveUrl string `json:"drive_url" bson:"drive_url"`
|
||||||
|
//Требуется GET параметр with. Текущая версия amoCRM
|
||||||
|
Version int `json:"version" bson:"version"`
|
||||||
|
UUID string `json:"uuid" bson:"uuid"`
|
||||||
|
IsApiFilterEnabled bool `json:"is_api_filter_enabled" bson:"is_api_filter_enabled"`
|
||||||
|
Links struct {
|
||||||
|
Self struct {
|
||||||
|
Href string `json:"href" bson:"href"`
|
||||||
|
} `json:"self" bson:"self"`
|
||||||
|
} `json:"_links" bson:"_links"`
|
||||||
|
Embedded struct {
|
||||||
|
AmojoRights struct {
|
||||||
|
CanDirect bool `json:"can_direct" bson:"can_direct"`
|
||||||
|
CanCreateGroups bool `json:"can_create_groups" bson:"can_create_groups"`
|
||||||
|
} `json:"amojo_rights" bson:"amojo_rights"`
|
||||||
|
//Требуется GET параметр with. Массив объектов групп пользователей аккаунта
|
||||||
|
UsersGroups []struct {
|
||||||
|
ID int `json:"id" bson:"id"`
|
||||||
|
Name string `json:"name" bson:"name"`
|
||||||
|
UUID interface{} `json:"uuid" bson:"uuid"`
|
||||||
|
} `json:"users_groups" bson:"users_groups"`
|
||||||
|
TaskTypes []struct {
|
||||||
|
ID int `json:"id" bson:"id"`
|
||||||
|
Name string `json:"name" bson:"name"`
|
||||||
|
Color interface{} `json:"color" bson:"color"`
|
||||||
|
IconID interface{} `json:"icon_id" bson:"icon_id"`
|
||||||
|
Code string `json:"code" bson:"code"`
|
||||||
|
} `json:"task_types" bson:"task_types"`
|
||||||
|
// Требуется GET параметр with. Настройки названия сущностей
|
||||||
|
EntityNames struct {
|
||||||
|
Leads struct {
|
||||||
|
Ru struct {
|
||||||
|
Gender string `json:"gender" bson:"gender"`
|
||||||
|
PluralForm struct {
|
||||||
|
Dative string `json:"dative" bson:"dative"`
|
||||||
|
Default string `json:"default" bson:"default"`
|
||||||
|
Genitive string `json:"genitive" bson:"genitive"`
|
||||||
|
Accusative string `json:"accusative" bson:"accusative"`
|
||||||
|
Instrumental string `json:"instrumental" bson:"instrumental"`
|
||||||
|
Prepositional string `json:"prepositional" bson:"prepositional"`
|
||||||
|
} `json:"plural_form" bson:"plural_form"`
|
||||||
|
SingularForm struct {
|
||||||
|
Dative string `json:"dative" bson:"dative"`
|
||||||
|
Default string `json:"default" bson:"default"`
|
||||||
|
Genitive string `json:"genitive" bson:"genitive"`
|
||||||
|
Accusative string `json:"accusative" bson:"accusative"`
|
||||||
|
Instrumental string `json:"instrumental" bson:"instrumental"`
|
||||||
|
Prepositional string `json:"prepositional" bson:"prepositional"`
|
||||||
|
} `json:"singular_form" bson:"singular_form"`
|
||||||
|
} `json:"ru" bson:"ru"`
|
||||||
|
En struct {
|
||||||
|
SingularForm struct {
|
||||||
|
Default string `json:"default" bson:"default"`
|
||||||
|
} `json:"singular_form" bson:"singular_form"`
|
||||||
|
PluralForm struct {
|
||||||
|
Default string `json:"default" bson:"default"`
|
||||||
|
} `json:"plural_form" bson:"plural_form"`
|
||||||
|
Gender string `json:"gender" bson:"gender"`
|
||||||
|
} `json:"en" bson:"en"`
|
||||||
|
Es struct {
|
||||||
|
SingularForm struct {
|
||||||
|
Default string `json:"default" bson:"default"`
|
||||||
|
} `json:"singular_form" bson:"singular_form"`
|
||||||
|
PluralForm struct {
|
||||||
|
Default string `json:"default" bson:"default"`
|
||||||
|
} `json:"plural_form" bson:"plural_form"`
|
||||||
|
Gender string `json:"gender" bson:"gender"`
|
||||||
|
} `json:"es" bson:"es"`
|
||||||
|
} `json:"leads" bson:"leads"`
|
||||||
|
} `json:"entity_names" bson:"entity_names"`
|
||||||
|
DatetimeSettings struct {
|
||||||
|
DatePattern string `json:"date_pattern" bson:"date_pattern"`
|
||||||
|
ShortDatePattern string `json:"short_date_pattern" bson:"short_date_pattern"`
|
||||||
|
ShortTimePattern string `json:"short_time_pattern" bson:"short_time_pattern"`
|
||||||
|
DateFormant string `json:"date_formant" bson:"date_formant"`
|
||||||
|
TimeFormat string `json:"time_format" bson:"time_format"`
|
||||||
|
Timezone string `json:"timezone" bson:"timezone"`
|
||||||
|
TimezoneOffset string `json:"timezone_offset" bson:"timezone_offset"`
|
||||||
|
} `json:"datetime_settings" bson:"datetime_settings"`
|
||||||
|
} `json:"_embedded" bson:"_embedded"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LeadRights struct {
|
||||||
|
View string `json:"view"`
|
||||||
|
Edit string `json:"edit"`
|
||||||
|
Add string `json:"add"`
|
||||||
|
Delete string `json:"delete"`
|
||||||
|
Export string `json:"export"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContactRights struct {
|
||||||
|
View string `json:"view"`
|
||||||
|
Edit string `json:"edit"`
|
||||||
|
Add string `json:"add"`
|
||||||
|
Delete string `json:"delete"`
|
||||||
|
Export string `json:"export"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompanyRights struct {
|
||||||
|
View string `json:"view"`
|
||||||
|
Edit string `json:"edit"`
|
||||||
|
Add string `json:"add"`
|
||||||
|
Delete string `json:"delete"`
|
||||||
|
Export string `json:"export"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskRights struct {
|
||||||
|
Edit string `json:"edit"`
|
||||||
|
Delete string `json:"delete"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusRight struct {
|
||||||
|
EntityType string `json:"entity_type"`
|
||||||
|
PipelineID int `json:"pipeline_id"`
|
||||||
|
StatusID int `json:"status_id"`
|
||||||
|
Rights LeadRights `json:"rights"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OneUserInfo struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Lang string `json:"lang"`
|
||||||
|
Role *string `json:"role,omitempty"`
|
||||||
|
UUID *string `json:"uuid,omitempty"`
|
||||||
|
Rights Rights `json:"rights"`
|
||||||
|
Links struct {
|
||||||
|
Self struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
} `json:"self"`
|
||||||
|
} `json:"_links"`
|
||||||
|
}
|
165
internal/proto/socialauth/models.pb.go
Normal file
165
internal/proto/socialauth/models.pb.go
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.31.0
|
||||||
|
// protoc (unknown)
|
||||||
|
// source: social_auth/v1/models.proto
|
||||||
|
|
||||||
|
package socialauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
State string `protobuf:"bytes,1,opt,name=State,proto3" json:"State,omitempty"`
|
||||||
|
ReturnURL string `protobuf:"bytes,2,opt,name=ReturnURL,proto3" json:"ReturnURL,omitempty"`
|
||||||
|
AccessToken *string `protobuf:"bytes,3,opt,name=AccessToken,proto3,oneof" json:"AccessToken,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Message) Reset() {
|
||||||
|
*x = Message{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_social_auth_v1_models_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Message) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Message) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Message) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_social_auth_v1_models_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Message.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Message) Descriptor() ([]byte, []int) {
|
||||||
|
return file_social_auth_v1_models_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Message) GetState() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.State
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Message) GetReturnURL() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ReturnURL
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Message) GetAccessToken() string {
|
||||||
|
if x != nil && x.AccessToken != nil {
|
||||||
|
return *x.AccessToken
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_social_auth_v1_models_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_social_auth_v1_models_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x1b, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x76, 0x31,
|
||||||
|
0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x73,
|
||||||
|
0x6f, 0x63, 0x69, 0x61, 0x6c, 0x61, 0x75, 0x74, 0x68, 0x22, 0x74, 0x0a, 0x07, 0x4d, 0x65, 0x73,
|
||||||
|
0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20,
|
||||||
|
0x01, 0x28, 0x09, 0x52, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x52, 0x65,
|
||||||
|
0x74, 0x75, 0x72, 0x6e, 0x55, 0x52, 0x4c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x52,
|
||||||
|
0x65, 0x74, 0x75, 0x72, 0x6e, 0x55, 0x52, 0x4c, 0x12, 0x25, 0x0a, 0x0b, 0x41, 0x63, 0x63, 0x65,
|
||||||
|
0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,
|
||||||
|
0x0b, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x88, 0x01, 0x01, 0x42,
|
||||||
|
0x0e, 0x0a, 0x0c, 0x5f, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42,
|
||||||
|
0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x6c, 0x61, 0x75, 0x74, 0x68, 0x62,
|
||||||
|
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_social_auth_v1_models_proto_rawDescOnce sync.Once
|
||||||
|
file_social_auth_v1_models_proto_rawDescData = file_social_auth_v1_models_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_social_auth_v1_models_proto_rawDescGZIP() []byte {
|
||||||
|
file_social_auth_v1_models_proto_rawDescOnce.Do(func() {
|
||||||
|
file_social_auth_v1_models_proto_rawDescData = protoimpl.X.CompressGZIP(file_social_auth_v1_models_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_social_auth_v1_models_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_social_auth_v1_models_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||||
|
var file_social_auth_v1_models_proto_goTypes = []interface{}{
|
||||||
|
(*Message)(nil), // 0: socialauth.Message
|
||||||
|
}
|
||||||
|
var file_social_auth_v1_models_proto_depIdxs = []int32{
|
||||||
|
0, // [0:0] is the sub-list for method output_type
|
||||||
|
0, // [0:0] is the sub-list for method input_type
|
||||||
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
|
0, // [0:0] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_social_auth_v1_models_proto_init() }
|
||||||
|
func file_social_auth_v1_models_proto_init() {
|
||||||
|
if File_social_auth_v1_models_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_social_auth_v1_models_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Message); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_social_auth_v1_models_proto_msgTypes[0].OneofWrappers = []interface{}{}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_social_auth_v1_models_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 1,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_social_auth_v1_models_proto_goTypes,
|
||||||
|
DependencyIndexes: file_social_auth_v1_models_proto_depIdxs,
|
||||||
|
MessageInfos: file_social_auth_v1_models_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_social_auth_v1_models_proto = out.File
|
||||||
|
file_social_auth_v1_models_proto_rawDesc = nil
|
||||||
|
file_social_auth_v1_models_proto_goTypes = nil
|
||||||
|
file_social_auth_v1_models_proto_depIdxs = nil
|
||||||
|
}
|
139
internal/repository/redis_repo.go
Normal file
139
internal/repository/redis_repo.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Repository struct {
|
||||||
|
redisClient *redis.Client
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
RedisClient *redis.Client
|
||||||
|
Logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRepository(deps Deps) *Repository {
|
||||||
|
return &Repository{
|
||||||
|
redisClient: deps.RedisClient,
|
||||||
|
logger: deps.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) CachingDealToRedis(ctx context.Context, deps models.SaveDeal) error {
|
||||||
|
key := "deal:" + strconv.FormatInt(deps.AnswerID, 10) + ":" + strconv.Itoa(int(deps.DealID))
|
||||||
|
valueJson, err := json.Marshal(deps)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.redisClient.Set(ctx, key, valueJson, 0).Err()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) CachingLeadFieldsToRedis(ctx context.Context, answerID int64, leadFields []models.FieldsValues) error {
|
||||||
|
key := strconv.FormatInt(answerID, 10)
|
||||||
|
leadFieldsJson, err := json.Marshal(leadFields)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.redisClient.Set(ctx, key, leadFieldsJson, 0).Err()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) FetchingDeals(ctx context.Context) (map[string][]models.MappingDealsData, map[int32]models.ForRestoringData, error) {
|
||||||
|
keys, err := r.redisClient.Keys(ctx, "deal:*").Result()
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("error fetching keys from Redis", zap.Error(err))
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
dealsDataForUpdate = make(map[string][]models.MappingDealsData)
|
||||||
|
forRestoringMap = make(map[int32]models.ForRestoringData)
|
||||||
|
wg sync.WaitGroup
|
||||||
|
)
|
||||||
|
|
||||||
|
wg.Add(len(keys))
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
go func(key string) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
saveDealJSON, err := r.redisClient.GetDel(ctx, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("error getting saveDeal JSON from Redis", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var saveDeal models.SaveDeal
|
||||||
|
err = json.Unmarshal([]byte(saveDealJSON), &saveDeal)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("error unmarshal saveDeal JSON", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
answerIDStr := strconv.FormatInt(saveDeal.AnswerID, 10)
|
||||||
|
|
||||||
|
leadFieldsJSON, err := r.redisClient.GetDel(ctx, answerIDStr).Result()
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("error getting leadFields JSON from Redis", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var leadFields []models.FieldsValues
|
||||||
|
err = json.Unmarshal([]byte(leadFieldsJSON), &leadFields)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Error("error unmarshal leadFields JSON", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("CUSTOM ENCODER", leadFields)
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
dealsDataForUpdate[saveDeal.AccessToken] = append(dealsDataForUpdate[saveDeal.AccessToken], models.MappingDealsData{
|
||||||
|
AnswerID: saveDeal.AnswerID,
|
||||||
|
DealID: saveDeal.DealID,
|
||||||
|
LeadFields: leadFields,
|
||||||
|
SubDomain: saveDeal.SubDomain,
|
||||||
|
})
|
||||||
|
|
||||||
|
forRestoringMap[saveDeal.DealID] = models.ForRestoringData{
|
||||||
|
SaveDeal: saveDeal,
|
||||||
|
LeadFields: leadFields,
|
||||||
|
}
|
||||||
|
}(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return dealsDataForUpdate, forRestoringMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) Close(_ context.Context) error {
|
||||||
|
err := r.redisClient.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
66
internal/server/http/http.go
Normal file
66
internal/server/http/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("/amocrm", 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 (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"amocrm/internal/tools"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"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.UserListFieldsResp, error) {
|
||||||
|
response, err := s.repository.AmoRepo.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
|
||||||
|
}
|
35
internal/service/initial.go
Normal file
35
internal/service/initial.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/brokers"
|
||||||
|
"amocrm/pkg/amoClient"
|
||||||
|
pena_social_auth "amocrm/pkg/pena-social-auth"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
Repository *dal.AmoDal
|
||||||
|
Logger *zap.Logger
|
||||||
|
SocialAuthClient *pena_social_auth.Client
|
||||||
|
AmoClient *amoClient.Amo
|
||||||
|
Producer *brokers.Producer
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
repository *dal.AmoDal
|
||||||
|
logger *zap.Logger
|
||||||
|
socialAuthClient *pena_social_auth.Client
|
||||||
|
amoClient *amoClient.Amo
|
||||||
|
producer *brokers.Producer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(deps Deps) *Service {
|
||||||
|
return &Service{
|
||||||
|
repository: deps.Repository,
|
||||||
|
logger: deps.Logger,
|
||||||
|
socialAuthClient: deps.SocialAuthClient,
|
||||||
|
amoClient: deps.AmoClient,
|
||||||
|
producer: deps.Producer,
|
||||||
|
}
|
||||||
|
}
|
37
internal/service/pipelines.go
Normal file
37
internal/service/pipelines.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"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.UserListPipelinesResp, error) {
|
||||||
|
response, err := s.repository.AmoRepo.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
|
||||||
|
}
|
88
internal/service/rules.go
Normal file
88
internal/service/rules.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"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.RulesReq, accountID string, quizID int) error {
|
||||||
|
err := s.repository.AmoRepo.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,
|
||||||
|
StepID: request.StepID,
|
||||||
|
Fieldsrule: request.Fieldsrule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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.RulesReq, accountID string, quizID int) error {
|
||||||
|
err := s.repository.AmoRepo.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,
|
||||||
|
StepID: request.StepID,
|
||||||
|
Fieldsrule: request.Fieldsrule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Rule, error) {
|
||||||
|
rule, err := s.repository.AmoRepo.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 (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"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, pipelineID int) (*model.UserListStepsResp, error) {
|
||||||
|
response, err := s.repository.AmoRepo.GetStepsWithPagination(ctx, req, accountID, int32(pipelineID))
|
||||||
|
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.PipelinesUpdate,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
37
internal/service/tags.go
Normal file
37
internal/service/tags.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) GetTagsWithPagination(ctx context.Context, req *model.PaginationReq, accountID string) (*model.UserListTagsResp, error) {
|
||||||
|
response, err := s.repository.AmoRepo.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
|
||||||
|
}
|
70
internal/service/user.go
Normal file
70
internal/service/user.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"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.UserListResp, error) {
|
||||||
|
response, err := s.repository.AmoRepo.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.AmoRepo.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.AmoAccount, error) {
|
||||||
|
user, err := s.repository.AmoRepo.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) {
|
||||||
|
link, err := s.socialAuthClient.GenerateAmocrmAuthURL(accountID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("error sending request to pena social auth service:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := model.ConnectAccountResp{
|
||||||
|
Link: link,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
34
internal/service/utm.go
Normal file
34
internal/service/utm.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
//func (s *Service) DeletingUserUtm(ctx context.Context, request *model.ListDeleteUTMIDsReq) error {
|
||||||
|
// err := s.repository.AmoRepo.DeletingUserUtm(ctx, request)
|
||||||
|
// if err != nil {
|
||||||
|
// s.logger.Error("error deleting user utm", zap.Error(err))
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (s *Service) SavingUserUtm(ctx context.Context, request *model.SaveUserListUTMReq, accountID string, quizID int) (*model.ListSavedIDUTMResp, error) {
|
||||||
|
// var utms []model.UTM
|
||||||
|
// for _, utm := range request.Utms {
|
||||||
|
// utm.Quizid = int32(quizID)
|
||||||
|
// utms = append(utms, utm)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// response, err := s.repository.AmoRepo.SavingUserUtm(ctx, utms, accountID)
|
||||||
|
// if err != nil {
|
||||||
|
// s.logger.Error("error saving user utm", zap.Error(err))
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// return response, nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func (s *Service) GettingUserUtm(ctx context.Context, request *model.PaginationReq, accountID string, quizID int) (*model.GetListUserUTMResp, error) {
|
||||||
|
// response, err := s.repository.AmoRepo.GettingUserUtm(ctx, request, accountID, quizID)
|
||||||
|
// if err != nil {
|
||||||
|
// s.logger.Error("error getting user utm with pagination", zap.Error(err))
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// return response, nil
|
||||||
|
//}
|
65
internal/service/webhook.go
Normal file
65
internal/service/webhook.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ParamsWebhookCreate struct {
|
||||||
|
Code string // Authorization 20 минут
|
||||||
|
Referer string // адрес аккаунта пользователя
|
||||||
|
AccountID string // строка которая передавалась в соц аус сервисе
|
||||||
|
FromWidget string
|
||||||
|
Platform string // ru/global 1/2
|
||||||
|
}
|
||||||
|
|
||||||
|
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 amo 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.Referer,
|
||||||
|
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.Referer,
|
||||||
|
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, amoID int) error {
|
||||||
|
err := s.repository.AmoRepo.WebhookDelete(ctx, amoID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("error canceled amo integration", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
174
internal/tools/construct.go
Normal file
174
internal/tools/construct.go
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"fmt"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToPipeline(amoPipelines []models.Pipeline) []model.Pipeline {
|
||||||
|
var pipelines []model.Pipeline
|
||||||
|
for _, p := range amoPipelines {
|
||||||
|
pipeline := model.Pipeline{
|
||||||
|
Amoid: int32(p.ID),
|
||||||
|
Name: p.Name,
|
||||||
|
Isarchive: p.IsArchive,
|
||||||
|
AccountID: int32(p.AccountID),
|
||||||
|
}
|
||||||
|
pipelines = append(pipelines, pipeline)
|
||||||
|
}
|
||||||
|
return pipelines
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToStep(amoStatuses []models.Statuses) []model.Step {
|
||||||
|
var steps []model.Step
|
||||||
|
for _, s := range amoStatuses {
|
||||||
|
step := model.Step{
|
||||||
|
Amoid: int32(s.ID),
|
||||||
|
Name: s.Name,
|
||||||
|
Pipelineid: int32(s.PipelineID),
|
||||||
|
Color: s.Color,
|
||||||
|
Accountid: int32(s.AccountID),
|
||||||
|
}
|
||||||
|
steps = append(steps, step)
|
||||||
|
}
|
||||||
|
return steps
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToTag(amoTag []models.Tag, entity model.EntityType) []model.Tag {
|
||||||
|
var tags []model.Tag
|
||||||
|
for _, t := range amoTag {
|
||||||
|
tag := model.Tag{
|
||||||
|
Amoid: int32(t.ID),
|
||||||
|
Entity: entity,
|
||||||
|
Name: t.Name,
|
||||||
|
Color: t.Color,
|
||||||
|
}
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToField(amoField []models.CustomField, entity model.EntityType) []model.Field {
|
||||||
|
var fields []model.Field
|
||||||
|
for _, f := range amoField {
|
||||||
|
field := model.Field{
|
||||||
|
Amoid: int32(f.ID),
|
||||||
|
Code: f.Code,
|
||||||
|
Name: f.Name,
|
||||||
|
Entity: entity,
|
||||||
|
Type: model.FieldType(f.Type),
|
||||||
|
}
|
||||||
|
fields = append(fields, field)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddContactFields(contactFields []models.FieldsValues, fieldValue string, fieldType model.ContactQuizConfig, fieldMap map[string]int) []models.FieldsValues {
|
||||||
|
if fieldValue != "" && fieldMap[string(fieldType)] != 0 {
|
||||||
|
values := make([]models.ValueInterface, 0)
|
||||||
|
values = append(values, models.Values{Value: fieldValue})
|
||||||
|
|
||||||
|
contactFields = append(contactFields, models.FieldsValues{
|
||||||
|
FieldID: fieldMap[string(fieldType)],
|
||||||
|
Values: values,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return contactFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConstructUTMFields(utmMap model.UTMSavingMap, currentFields []model.Field) []models.FieldsValues {
|
||||||
|
var fields []models.FieldsValues
|
||||||
|
for _, field := range currentFields {
|
||||||
|
if data, ok := utmMap[field.Name]; ok {
|
||||||
|
val := []models.ValueInterface{
|
||||||
|
models.Values{
|
||||||
|
Value: data,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
f := models.FieldsValues{
|
||||||
|
FieldID: int(field.Amoid),
|
||||||
|
Values: val,
|
||||||
|
}
|
||||||
|
fields = append(fields, f)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConstructAmoTags(currentTags []model.Tag, ruleTags model.TagsToAdd) []models.Tag {
|
||||||
|
var tagsToAmo []models.Tag
|
||||||
|
ruleTagMap := make(map[int64]struct{})
|
||||||
|
|
||||||
|
mapConstruct := func(idsArray []int64) {
|
||||||
|
for _, id := range idsArray {
|
||||||
|
ruleTagMap[id] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mapConstruct(ruleTags.Lead)
|
||||||
|
mapConstruct(ruleTags.Contact)
|
||||||
|
mapConstruct(ruleTags.Company)
|
||||||
|
mapConstruct(ruleTags.Customer)
|
||||||
|
|
||||||
|
for _, tag := range currentTags {
|
||||||
|
if _, ok := ruleTagMap[int64(tag.Amoid)]; ok {
|
||||||
|
tagsToAmo = append(tagsToAmo, models.Tag{
|
||||||
|
ID: int(tag.Amoid),
|
||||||
|
Name: tag.Name,
|
||||||
|
//Color: tag.Color,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tagsToAmo
|
||||||
|
}
|
100
internal/tools/for_rules.go
Normal file
100
internal/tools/for_rules.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"fmt"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ToUpdate struct {
|
||||||
|
FieldID int
|
||||||
|
Entity model.EntityType
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToCreatedUpdateQuestionRules(questionsTypeMap map[model.EntityType][]model.Question, currentFields []model.Field) (map[model.EntityType][]models.AddLeadsFields, map[int]ToUpdate) {
|
||||||
|
toUpdate := make(map[int]ToUpdate) // на обновление ключ id вопроса значение id кастомного поля для тех у кого имя совпадает
|
||||||
|
toCreated := make(map[model.EntityType][]models.AddLeadsFields)
|
||||||
|
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.Name, " ", ""))
|
||||||
|
if title == fieldName && entity == field.Entity {
|
||||||
|
toUpdate[int(question.Id)] = ToUpdate{
|
||||||
|
FieldID: int(field.Amoid),
|
||||||
|
Entity: entity,
|
||||||
|
}
|
||||||
|
matched = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !matched {
|
||||||
|
//Type: model.TypeMapping[question.Type]
|
||||||
|
fieldType := model.TypeAmoTextarea
|
||||||
|
if question.Type == model.TypeFile {
|
||||||
|
fieldType = model.TypeAmoFile
|
||||||
|
}
|
||||||
|
toCreated[entity] = append(toCreated[entity], models.AddLeadsFields{Type: fieldType, Name: question.Title})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return toCreated, toUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToQuestionIDs(rule map[int]int) []int32 {
|
||||||
|
var ids []int32
|
||||||
|
for queID, fieldID := range rule {
|
||||||
|
if fieldID != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ids = append(ids, int32(queID))
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
func ForContactRules(quizConfig model.QuizContact, currentFields []model.Field) ([]models.AddLeadsFields, map[string]int) {
|
||||||
|
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]int)
|
||||||
|
var toCreated []models.AddLeadsFields
|
||||||
|
for _, contactField := range contactFieldsArr {
|
||||||
|
matched := false
|
||||||
|
for _, field := range currentFields {
|
||||||
|
if field.Name == string(contactField) && field.Entity == model.ContactsType {
|
||||||
|
matched = true
|
||||||
|
forAdding[string(contactField)] = int(field.Amoid)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !matched {
|
||||||
|
//Type: model.TypeMapping[question.Type]
|
||||||
|
toCreated = append(toCreated, models.AddLeadsFields{Type: model.TypeAmoText, Name: string(contactField)})
|
||||||
|
forAdding[string(contactField)] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return toCreated, forAdding
|
||||||
|
}
|
29
internal/tools/groups.go
Normal file
29
internal/tools/groups.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConvertUserGroups(groups *models.AmocrmUserInformation) []model.UserGroups {
|
||||||
|
var userGroups []model.UserGroups
|
||||||
|
for _, group := range groups.Embedded.UsersGroups {
|
||||||
|
userGroups = append(userGroups, model.UserGroups{
|
||||||
|
ID: group.ID,
|
||||||
|
Name: group.Name,
|
||||||
|
UUID: group.UUID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return userGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
//func ConvertGroups(groups models.Users) []model.UserGroups {
|
||||||
|
// var userGroups []model.UserGroups
|
||||||
|
// for _, group := range groups.Embedded.Groups {
|
||||||
|
// userGroups = append(userGroups, model.UserGroups{
|
||||||
|
// ID: group.ID,
|
||||||
|
// Name: group.Name,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// return userGroups
|
||||||
|
//}
|
25
internal/tools/proto.go
Normal file
25
internal/tools/proto.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/proto/socialauth"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DeserializeProtobufMessage(protobufMessage string) (string, string, error) {
|
||||||
|
msg := socialauth.Message{}
|
||||||
|
|
||||||
|
err := proto.Unmarshal([]byte(protobufMessage), &msg)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("PROTOOTOT", msg.State, *msg.AccessToken)
|
||||||
|
|
||||||
|
var accountID string
|
||||||
|
if msg.AccessToken != nil {
|
||||||
|
accountID = *msg.AccessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountID, msg.ReturnURL, nil
|
||||||
|
}
|
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.UserListFieldsResp) *model.UserListFieldsResp {
|
||||||
|
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.UserListFieldsResp{
|
||||||
|
Count: response.Count,
|
||||||
|
Items: []model.Field{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range response.Items {
|
||||||
|
if _, ok := checkUTM[r.Name]; !ok {
|
||||||
|
data.Items = append(data.Items, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
40
internal/tools/verify.go
Normal file
40
internal/tools/verify.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Verify struct {
|
||||||
|
integrationSecret string
|
||||||
|
integrationID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVerify(integrationSecret, integrationID string) *Verify {
|
||||||
|
return &Verify{
|
||||||
|
integrationSecret: integrationSecret,
|
||||||
|
integrationID: integrationID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Verify) VerifySignature(clientUUID, signature string, amoID int) bool {
|
||||||
|
expected := v.getSignature(clientUUID, amoID)
|
||||||
|
return hmac.Equal([]byte(signature), []byte(expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Verify) getSignature(clientUUID string, amoID int) string {
|
||||||
|
message := fmt.Sprintf("%s|%d", clientUUID, amoID)
|
||||||
|
h := hmac.New(sha256.New, []byte(v.integrationSecret))
|
||||||
|
h.Write([]byte(message))
|
||||||
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Verify) CheckIntegrationID(clientUUID string) bool {
|
||||||
|
if v.integrationID != clientUUID {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
59
internal/workers/data_updater/data_updater.go
Normal file
59
internal/workers/data_updater/data_updater.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package data_updater
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/brokers"
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"context"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"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) {
|
||||||
|
nextStart := calculateTime()
|
||||||
|
ticker := time.NewTicker(time.Nanosecond * time.Duration(nextStart))
|
||||||
|
//ticker := time.NewTicker(10 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
wc.processTasks(ctx)
|
||||||
|
nextStart = calculateTime()
|
||||||
|
ticker.Reset(time.Nanosecond * time.Duration(nextStart))
|
||||||
|
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
|
||||||
|
}
|
19
internal/workers/data_updater/timer.go
Normal file
19
internal/workers/data_updater/timer.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package data_updater
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func calculateTime() int64 {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
targetTime := time.Date(now.Year(), now.Month(), now.Day(), 4, 0, 0, 0, now.Location())
|
||||||
|
if now.After(targetTime) {
|
||||||
|
targetTime = targetTime.AddDate(0, 0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
toTarget := targetTime.Sub(now)
|
||||||
|
sec := toTarget.Nanoseconds()
|
||||||
|
|
||||||
|
return sec
|
||||||
|
}
|
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
|
||||||
|
}
|
737
internal/workers/post_deals_worker/deals_worker.go
Normal file
737
internal/workers/post_deals_worker/deals_worker.go
Normal file
@ -0,0 +1,737 @@
|
|||||||
|
package post_deals_worker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"amocrm/internal/repository"
|
||||||
|
"amocrm/internal/tools"
|
||||||
|
"amocrm/pkg/amoClient"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"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/amo"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/utils"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
AmoRepo *dal.AmoDal
|
||||||
|
AmoClient *amoClient.Amo
|
||||||
|
RedisRepo *repository.Repository
|
||||||
|
Logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostDeals struct {
|
||||||
|
amoRepo *dal.AmoDal
|
||||||
|
amoClient *amoClient.Amo
|
||||||
|
redisRepo *repository.Repository
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPostDealsWC(deps Deps) *PostDeals {
|
||||||
|
return &PostDeals{
|
||||||
|
amoRepo: deps.AmoRepo,
|
||||||
|
amoClient: deps.AmoClient,
|
||||||
|
redisRepo: deps.RedisRepo,
|
||||||
|
logger: deps.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *PostDeals) 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 *PostDeals) startFetching(ctx context.Context) {
|
||||||
|
results, err := wc.amoRepo.AmoRepo.GettingAmoUsersTrueResults(ctx)
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error fetching users answers true results, for sending data to amo", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mapDealReq := make(map[string][]models.DealReq)
|
||||||
|
mapTokenDomain := make(map[string]string)
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
userPrivileges, err := wc.amoRepo.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.amoRepo.AnswerRepo.GetAllAnswersByQuizID(ctx, result.Session)
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error getting all user answers by result session", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// За один запрос можно передать не более 50 сделок.
|
||||||
|
deal := models.DealReq{
|
||||||
|
Name: fmt.Sprintf("deal quiz number %d", result.QuizID),
|
||||||
|
StatusID: result.StepID,
|
||||||
|
PipelineID: result.PipelineID,
|
||||||
|
CreatedBy: 0, //result.AmoAccountID,
|
||||||
|
UpdatedBy: 0,
|
||||||
|
CreatedAt: time.Now().Unix(),
|
||||||
|
ResponsibleUserID: result.PerformerID,
|
||||||
|
Embed: models.Embedd{
|
||||||
|
Company: []models.Company{},
|
||||||
|
Source: models.Source{
|
||||||
|
Type: "widget",
|
||||||
|
},
|
||||||
|
Tags: tools.ConstructAmoTags(userTags, result.TagsToAdd),
|
||||||
|
},
|
||||||
|
// строка которая будет возвращенна в респонсе чтоб понимать кто есть что
|
||||||
|
RequestID: strconv.Itoa(int(result.AnswerID)),
|
||||||
|
}
|
||||||
|
|
||||||
|
leadFields, contactData, companyData, customerToCreate, err := wc.constructField(ctx, allAnswers, result)
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error construct fields", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFields, err := wc.amoRepo.AmoRepo.GetUserFieldsByID(ctx, result.AmoAccountID)
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error getting current user fields from db", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utmFields := tools.ConstructUTMFields(result.UTMs, currentFields)
|
||||||
|
|
||||||
|
_, err = wc.amoClient.CreatingCustomer(customerToCreate, result.AccessToken, result.SubDomain)
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error sending requests for create customer", zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = wc.redisRepo.CachingLeadFieldsToRedis(ctx, result.AnswerID, leadFields)
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error saving leads fields in redis", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deal.Embed.Contact = contactData
|
||||||
|
deal.Embed.Company = companyData
|
||||||
|
deal.CustomFieldsValues = utmFields
|
||||||
|
|
||||||
|
wc.logger.Info("NOW DEAL CONSTRUCTED IS:", zap.Any("DEAL", deal))
|
||||||
|
|
||||||
|
if len(mapDealReq[result.AccessToken]) >= 49 {
|
||||||
|
wc.logger.Info("reached maximum number of deals for access token", zap.String("access_token", result.AccessToken))
|
||||||
|
err = wc.sendingDealsReq(ctx, mapDealReq, mapTokenDomain)
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error sending requests for create deals", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mapDealReq = make(map[string][]models.DealReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
mapDealReq[result.AccessToken] = append(mapDealReq[result.AccessToken], deal)
|
||||||
|
mapTokenDomain[result.AccessToken] = result.SubDomain
|
||||||
|
}
|
||||||
|
|
||||||
|
err = wc.sendingDealsReq(ctx, mapDealReq, mapTokenDomain)
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error send requests for create deals", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *PostDeals) sendingDealsReq(ctx context.Context, mapDealReq map[string][]models.DealReq, mapTokenDomain map[string]string) error {
|
||||||
|
for accessToken, deal := range mapDealReq {
|
||||||
|
subDomain := mapTokenDomain[accessToken]
|
||||||
|
resp, err := wc.amoClient.CreatingDeal(deal, accessToken, subDomain)
|
||||||
|
if err != nil {
|
||||||
|
// todo логирование в тг
|
||||||
|
wc.logger.Error("error creating deal in amo", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = wc.saveDealToDB(ctx, resp, accessToken, subDomain)
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error saving resp data to db", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *PostDeals) saveDealToDB(ctx context.Context, resp []models.DealResp, accessToken string, subDomain string) error {
|
||||||
|
status := "pending"
|
||||||
|
for _, dealResp := range resp {
|
||||||
|
requestID := strings.Join(dealResp.RequestID, ",")
|
||||||
|
answerID, err := strconv.ParseInt(requestID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error converting str requestID to int answerID", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = wc.amoRepo.AmoRepo.SaveDealAmoStatus(ctx, amo.SaveDealAmoDeps{DealID: dealResp.DealID, AnswerID: answerID, AccessToken: accessToken, Status: status})
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error saving deal status to database", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = wc.redisRepo.CachingDealToRedis(ctx, models.SaveDeal{
|
||||||
|
AnswerID: answerID,
|
||||||
|
DealID: dealResp.DealID,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
SubDomain: subDomain,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error saving deal to redis", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *PostDeals) constructField(ctx context.Context, allAnswers []model.ResultAnswer, result model.AmoUsersTrueResults) ([]models.FieldsValues, []models.Contact, []models.Company, []models.Customer, error) {
|
||||||
|
dateCreating := time.Now().Unix()
|
||||||
|
|
||||||
|
entityFieldsMap := make(map[model.EntityType]map[int][]models.ValueInterface)
|
||||||
|
entityFieldsMap[model.LeadsType] = make(map[int][]models.ValueInterface)
|
||||||
|
entityFieldsMap[model.CompaniesType] = make(map[int][]models.ValueInterface)
|
||||||
|
entityFieldsMap[model.CustomersType] = make(map[int][]models.ValueInterface)
|
||||||
|
entityFieldsMap[model.ContactsType] = make(map[int][]models.ValueInterface)
|
||||||
|
|
||||||
|
entityRules := make(map[model.EntityType]map[int]int)
|
||||||
|
entityRules[model.LeadsType] = result.FieldsRule.Lead.Questionid
|
||||||
|
entityRules[model.CompaniesType] = result.FieldsRule.Company.Questionid
|
||||||
|
entityRules[model.CustomersType] = result.FieldsRule.Customer.Questionid
|
||||||
|
entityRules[model.ContactsType] = result.FieldsRule.Contact.Questionid
|
||||||
|
|
||||||
|
for entityType, rule := range entityRules {
|
||||||
|
for _, data := range allAnswers {
|
||||||
|
if fieldID, ok := rule[int(data.QuestionID)]; ok {
|
||||||
|
|
||||||
|
fieldData, err := wc.amoRepo.AmoRepo.GetFieldByID(ctx, int32(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, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldData.Type == model.TypeAmoText || fieldData.Type == model.TypeAmoTextarea {
|
||||||
|
values := entityFieldsMap[entityType][fieldID]
|
||||||
|
content := strings.ReplaceAll(data.Content, " ", "")
|
||||||
|
if content == "" {
|
||||||
|
data.Content = "Пустая строка"
|
||||||
|
}
|
||||||
|
values = append(values, models.Values{Value: tools.EmojiUnicode(data.Content)})
|
||||||
|
entityFieldsMap[entityType][fieldID] = values
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldData.Type == model.TypeFile && data.Content != "" && result.DriveURL != "" {
|
||||||
|
value, err := wc.amoClient.UploadFileToAmo(data.Content, result.AccessToken, result.DriveURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
values := entityFieldsMap[entityType][fieldID]
|
||||||
|
values = append(values, value)
|
||||||
|
entityFieldsMap[entityType][fieldID] = values
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var leadFields []models.FieldsValues
|
||||||
|
var contactFields []models.FieldsValues
|
||||||
|
var companyFields []models.FieldsValues
|
||||||
|
var customerFields []models.FieldsValues
|
||||||
|
|
||||||
|
for entityType, fieldMap := range entityFieldsMap {
|
||||||
|
for fieldID, values := range fieldMap {
|
||||||
|
field := models.FieldsValues{
|
||||||
|
FieldID: fieldID,
|
||||||
|
Values: values,
|
||||||
|
}
|
||||||
|
switch entityType {
|
||||||
|
case model.LeadsType:
|
||||||
|
leadFields = append(leadFields, field)
|
||||||
|
case model.CompaniesType:
|
||||||
|
companyFields = append(companyFields, field)
|
||||||
|
case model.CustomersType:
|
||||||
|
customerFields = append(customerFields, field)
|
||||||
|
case model.ContactsType:
|
||||||
|
contactFields = append(contactFields, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultInfo model.ResultContent
|
||||||
|
err := json.Unmarshal([]byte(result.Content), &resultInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
var contactID int32
|
||||||
|
contactRuleMap := result.FieldsRule.Contact.ContactRuleMap
|
||||||
|
|
||||||
|
contactFields = tools.AddContactFields(contactFields, resultInfo.Name, model.TypeContactName, contactRuleMap)
|
||||||
|
if len(resultInfo.Phone) > 4 || resultInfo.Phone != "" {
|
||||||
|
contactFields = tools.AddContactFields(contactFields, resultInfo.Phone, model.TypeContactPhone, contactRuleMap)
|
||||||
|
}
|
||||||
|
contactFields = tools.AddContactFields(contactFields, resultInfo.Text, model.TypeContactText, contactRuleMap)
|
||||||
|
contactFields = tools.AddContactFields(contactFields, resultInfo.Email, model.TypeContactEmail, contactRuleMap)
|
||||||
|
contactFields = tools.AddContactFields(contactFields, resultInfo.Address, model.TypeContactAddress, contactRuleMap)
|
||||||
|
|
||||||
|
name := resultInfo.Name
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("empty name, quiz %d, triggered by answer - %d", result.QuizID, result.AnswerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var fields []string
|
||||||
|
if len(resultInfo.Phone) > 4 || resultInfo.Phone != "" {
|
||||||
|
fields = append(fields, resultInfo.Phone)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resultInfo.Email != "" {
|
||||||
|
fields = append(fields, resultInfo.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
existContactData, err := wc.amoRepo.AmoRepo.GetExistingContactAmo(ctx, result.AmoAccountID, fields)
|
||||||
|
if err != nil && !errors.Is(err, pj_errors.ErrNotFound) {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
if errors.Is(err, pj_errors.ErrNotFound) || len(existContactData) == 0 {
|
||||||
|
fmt.Println("NO CONTACT", contactFields)
|
||||||
|
contactResp, err := wc.amoClient.CreateContact([]models.CreateContactReq{
|
||||||
|
{
|
||||||
|
Name: resultInfo.Name,
|
||||||
|
ResponsibleUserID: result.PerformerID,
|
||||||
|
CreatedBy: 0,
|
||||||
|
UpdatedBy: 0,
|
||||||
|
CreatedAt: dateCreating,
|
||||||
|
CustomFieldsValues: contactFields,
|
||||||
|
},
|
||||||
|
}, result.SubDomain, result.AccessToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
for _, c := range contactResp.Embedded.Contacts {
|
||||||
|
contactID = c.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resultInfo.Phone) > 4 || resultInfo.Phone != "" {
|
||||||
|
_, err = wc.amoRepo.AmoRepo.InsertContactAmo(ctx, model.ContactAmo{
|
||||||
|
AccountID: result.AmoAccountID,
|
||||||
|
AmoID: contactID,
|
||||||
|
Field: resultInfo.Phone,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resultInfo.Email != "" {
|
||||||
|
_, err = wc.amoRepo.AmoRepo.InsertContactAmo(ctx, model.ContactAmo{
|
||||||
|
AccountID: result.AmoAccountID,
|
||||||
|
AmoID: contactID,
|
||||||
|
Field: resultInfo.Email,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if existContactData != nil && len(existContactData) > 0 {
|
||||||
|
contactID, err = wc.chooseAndCreateContact(ctx, result, resultInfo, existContactData, dateCreating, contactFields, contactRuleMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return leadFields, []models.Contact{
|
||||||
|
{
|
||||||
|
ID: contactID,
|
||||||
|
//Name: name,
|
||||||
|
//ResponsibleUserID: result.PerformerID,
|
||||||
|
//CreatedBy: 0,
|
||||||
|
//UpdatedBy: 0,
|
||||||
|
//CreatedAt: dateCreating,
|
||||||
|
//CustomFieldsValues: contactFields,
|
||||||
|
},
|
||||||
|
}, []models.Company{
|
||||||
|
{
|
||||||
|
Name: fmt.Sprintf("Компания %d", result.AnswerID),
|
||||||
|
ResponsibleUserID: result.PerformerID,
|
||||||
|
CreatedBy: 0,
|
||||||
|
UpdatedBy: 0,
|
||||||
|
CreatedAt: dateCreating,
|
||||||
|
CustomFieldsValues: companyFields,
|
||||||
|
},
|
||||||
|
}, []models.Customer{
|
||||||
|
{
|
||||||
|
// в амо имя покупателя не может быть пустым, надо как то с этим жить
|
||||||
|
Name: name,
|
||||||
|
ResponsibleUserID: result.PerformerID,
|
||||||
|
CreatedBy: 0,
|
||||||
|
UpdatedBy: 0,
|
||||||
|
CreatedAt: dateCreating,
|
||||||
|
CustomFields: customerFields,
|
||||||
|
RequestID: fmt.Sprint(result.AnswerID),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *PostDeals) chooseAndCreateContact(ctx context.Context, result model.AmoUsersTrueResults, resultInfo model.ResultContent, existingContacts map[int32][]model.ContactAmo, dateCreating int64, contactFields []models.FieldsValues, contactRuleMap map[string]int) (int32, 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.AmoID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, contactVariants := range existingContacts {
|
||||||
|
for _, contact := range contactVariants {
|
||||||
|
if contact.Field == resultInfo.Email {
|
||||||
|
if _, ok := phoneMatchedContacts[contact.AmoID]; ok {
|
||||||
|
fmt.Println("нашлось телефон и емайл в бд, с одинаковым амоид", resultInfo.Name, resultInfo.Phone, resultInfo.Email)
|
||||||
|
return contact.AmoID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var phoneContactID, emailContactID int32
|
||||||
|
var phoneID int64 /*emailID*/
|
||||||
|
for _, contactVariants := range existingContacts {
|
||||||
|
for _, contact := range contactVariants {
|
||||||
|
if contact.Field == resultInfo.Phone {
|
||||||
|
phoneContactID = contact.AmoID
|
||||||
|
phoneID = contact.ID
|
||||||
|
}
|
||||||
|
if contact.Field == resultInfo.Email {
|
||||||
|
emailContactID = contact.AmoID
|
||||||
|
//emailID = contact.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if phoneContactID != 0 && emailContactID != 0 && phoneContactID != emailContactID {
|
||||||
|
fmt.Println("нашлось телефон и емайл в бд, но это пока разные контакты", resultInfo.Name, resultInfo.Phone, resultInfo.Email)
|
||||||
|
// делаем обновление телефона там где уже есть email
|
||||||
|
var valuePhone []models.FieldsValues
|
||||||
|
valuePhone = tools.AddContactFields(valuePhone, resultInfo.Phone, model.TypeContactPhone, contactRuleMap)
|
||||||
|
name := resultInfo.Name
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("empty name, quiz %d, triggered by answer - %d", result.QuizID, result.AnswerID)
|
||||||
|
}
|
||||||
|
_, err := wc.amoClient.UpdateContact(models.CreateContactReq{
|
||||||
|
Name: name,
|
||||||
|
UpdatedBy: 0,
|
||||||
|
ResponsibleUserID: result.PerformerID,
|
||||||
|
CustomFieldsValues: valuePhone,
|
||||||
|
}, result.SubDomain, result.AccessToken, emailContactID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = wc.amoRepo.AmoRepo.UpdateAmoContact(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.AmoID] {
|
||||||
|
if variant.Field != contact.Field {
|
||||||
|
if variant.Field != "" {
|
||||||
|
emailExists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !emailExists && resultInfo.Email != "" {
|
||||||
|
// email пустой обновляем контакт добавляя email, если не пустой
|
||||||
|
fmt.Println("нашлось телефон, емайл не пустой, а в бд пустой. обновляем контакт", resultInfo.Name, resultInfo.Phone, resultInfo.Email)
|
||||||
|
var valueEmail []models.FieldsValues
|
||||||
|
valueEmail = tools.AddContactFields(valueEmail, resultInfo.Email, model.TypeContactEmail, contactRuleMap)
|
||||||
|
name := resultInfo.Name
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("empty name, quiz %d, triggered by answer - %d", result.QuizID, result.AnswerID)
|
||||||
|
}
|
||||||
|
_, err := wc.amoClient.UpdateContact(models.CreateContactReq{
|
||||||
|
Name: name,
|
||||||
|
UpdatedBy: 0,
|
||||||
|
ResponsibleUserID: result.PerformerID,
|
||||||
|
CustomFieldsValues: valueEmail,
|
||||||
|
}, result.SubDomain, result.AccessToken, contact.AmoID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
_, err = wc.amoRepo.AmoRepo.InsertContactAmo(ctx, model.ContactAmo{
|
||||||
|
AccountID: result.AmoAccountID,
|
||||||
|
AmoID: contact.AmoID,
|
||||||
|
Field: resultInfo.Email,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return contact.AmoID, nil
|
||||||
|
}
|
||||||
|
if emailExists && resultInfo.Email != "" {
|
||||||
|
// email не пустой значит это новый контакт создаем если наш email тоже не пустой
|
||||||
|
fmt.Println("нашлось телефон, емайл не пустой и в бд не пустой. создаем новый контакт", resultInfo.Name, resultInfo.Phone, resultInfo.Email)
|
||||||
|
name := resultInfo.Name
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("empty name, quiz %d, triggered by answer - %d", result.QuizID, result.AnswerID)
|
||||||
|
}
|
||||||
|
resp, err := wc.amoClient.CreateContact([]models.CreateContactReq{
|
||||||
|
{
|
||||||
|
Name: name,
|
||||||
|
ResponsibleUserID: result.PerformerID,
|
||||||
|
CreatedBy: 0,
|
||||||
|
UpdatedBy: 0,
|
||||||
|
CreatedAt: dateCreating,
|
||||||
|
CustomFieldsValues: contactFields,
|
||||||
|
},
|
||||||
|
}, result.SubDomain, result.AccessToken)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var contactID int32
|
||||||
|
for _, c := range resp.Embedded.Contacts {
|
||||||
|
contactID = c.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = wc.amoRepo.AmoRepo.InsertContactAmo(ctx, model.ContactAmo{
|
||||||
|
AccountID: result.AmoAccountID,
|
||||||
|
AmoID: contactID,
|
||||||
|
Field: resultInfo.Phone,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = wc.amoRepo.AmoRepo.InsertContactAmo(ctx, model.ContactAmo{
|
||||||
|
AccountID: result.AmoAccountID,
|
||||||
|
AmoID: contactID,
|
||||||
|
Field: resultInfo.Email,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return contactID, nil
|
||||||
|
}
|
||||||
|
// если пустой то это нужный контакт возвращаем его id, так как если мейл пустой у нас но номер совпадает а в бд не пустой значит оно нам надо
|
||||||
|
fmt.Println("нашлось телефон, емайл пустой возвращаем существующий контакт", resultInfo.Name, resultInfo.Phone, resultInfo.Email)
|
||||||
|
return contact.AmoID, 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.AmoID] {
|
||||||
|
if variant.Field != contact.Field {
|
||||||
|
if variant.Field != "" {
|
||||||
|
phoneExists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !phoneExists && (len(resultInfo.Phone) > 4 || resultInfo.Phone != "") {
|
||||||
|
// телефон пустой обновляем контакт добавляя телефон, если не пустой
|
||||||
|
fmt.Println("нашлось емайл, телефон не пустой, а в бд пустой. обновляем контакт", resultInfo.Name, resultInfo.Phone, resultInfo.Email)
|
||||||
|
var valuePhone []models.FieldsValues
|
||||||
|
valuePhone = tools.AddContactFields(valuePhone, resultInfo.Phone, model.TypeContactPhone, contactRuleMap)
|
||||||
|
name := resultInfo.Name
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("empty name, quiz %d, triggered by answer - %d", result.QuizID, result.AnswerID)
|
||||||
|
}
|
||||||
|
_, err := wc.amoClient.UpdateContact(models.CreateContactReq{
|
||||||
|
Name: name,
|
||||||
|
UpdatedBy: 0,
|
||||||
|
ResponsibleUserID: result.PerformerID,
|
||||||
|
CustomFieldsValues: valuePhone,
|
||||||
|
}, result.SubDomain, result.AccessToken, contact.AmoID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
_, err = wc.amoRepo.AmoRepo.InsertContactAmo(ctx, model.ContactAmo{
|
||||||
|
AccountID: result.AmoAccountID,
|
||||||
|
AmoID: contact.AmoID,
|
||||||
|
Field: resultInfo.Phone,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return contact.AmoID, nil
|
||||||
|
}
|
||||||
|
if phoneExists && (len(resultInfo.Phone) > 4 || resultInfo.Phone != "") {
|
||||||
|
// телефон не пустой значит это новый контакт создаем если наш телефон не пустой
|
||||||
|
fmt.Println("нашлось емайл, телефон не пустой и в бд не пустой. создаем новый контакт", resultInfo.Name, resultInfo.Phone, resultInfo.Email)
|
||||||
|
name := resultInfo.Name
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("empty name, quiz %d, triggered by answer - %d", result.QuizID, result.AnswerID)
|
||||||
|
}
|
||||||
|
resp, err := wc.amoClient.CreateContact([]models.CreateContactReq{
|
||||||
|
{
|
||||||
|
Name: name,
|
||||||
|
ResponsibleUserID: result.PerformerID,
|
||||||
|
CreatedBy: 0,
|
||||||
|
UpdatedBy: 0,
|
||||||
|
CreatedAt: dateCreating,
|
||||||
|
CustomFieldsValues: contactFields,
|
||||||
|
},
|
||||||
|
}, result.SubDomain, result.AccessToken)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var contactID int32
|
||||||
|
for _, c := range resp.Embedded.Contacts {
|
||||||
|
contactID = c.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = wc.amoRepo.AmoRepo.InsertContactAmo(ctx, model.ContactAmo{
|
||||||
|
AccountID: result.AmoAccountID,
|
||||||
|
AmoID: contactID,
|
||||||
|
Field: resultInfo.Phone,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = wc.amoRepo.AmoRepo.InsertContactAmo(ctx, model.ContactAmo{
|
||||||
|
AccountID: result.AmoAccountID,
|
||||||
|
AmoID: contactID,
|
||||||
|
Field: resultInfo.Email,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return contactID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// если пустой то это нужный контакт возвращаем его id, так как если телефон пустой у нас но мейл совпадает а в бд не пустой значит оно нам надо
|
||||||
|
fmt.Println("нашлось емайл, телефон пустой возвращаем существующий контакт", resultInfo.Name, resultInfo.Phone, resultInfo.Email)
|
||||||
|
return contact.AmoID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("ничего не нашлось, создаем новый контакт", resultInfo.Name, resultInfo.Phone, 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)
|
||||||
|
}
|
||||||
|
resp, err := wc.amoClient.CreateContact([]models.CreateContactReq{
|
||||||
|
{
|
||||||
|
Name: name,
|
||||||
|
ResponsibleUserID: result.PerformerID,
|
||||||
|
CreatedBy: 0,
|
||||||
|
UpdatedBy: 0,
|
||||||
|
CreatedAt: dateCreating,
|
||||||
|
CustomFieldsValues: contactFields,
|
||||||
|
},
|
||||||
|
}, result.SubDomain, result.AccessToken)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var contactID int32
|
||||||
|
for _, c := range resp.Embedded.Contacts {
|
||||||
|
contactID = c.ID
|
||||||
|
}
|
||||||
|
if len(resultInfo.Phone) > 4 || resultInfo.Phone != "" {
|
||||||
|
_, err = wc.amoRepo.AmoRepo.InsertContactAmo(ctx, model.ContactAmo{
|
||||||
|
AccountID: result.AmoAccountID,
|
||||||
|
AmoID: contactID,
|
||||||
|
Field: resultInfo.Phone,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resultInfo.Email != "" {
|
||||||
|
_, err = wc.amoRepo.AmoRepo.InsertContactAmo(ctx, model.ContactAmo{
|
||||||
|
AccountID: result.AmoAccountID,
|
||||||
|
AmoID: contactID,
|
||||||
|
Field: resultInfo.Email,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contactID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *PostDeals) Stop(_ context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
140
internal/workers/post_fields_worker/fields_worker.go
Normal file
140
internal/workers/post_fields_worker/fields_worker.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package post_fields_worker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"amocrm/internal/repository"
|
||||||
|
"amocrm/pkg/amoClient"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/repository/amo"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
AmoRepo *dal.AmoDal
|
||||||
|
AmoClient *amoClient.Amo
|
||||||
|
RedisRepo *repository.Repository
|
||||||
|
Logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostFields struct {
|
||||||
|
amoRepo *dal.AmoDal
|
||||||
|
amoClient *amoClient.Amo
|
||||||
|
redisRepo *repository.Repository
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPostFieldsWC(deps Deps) *PostFields {
|
||||||
|
return &PostFields{
|
||||||
|
amoRepo: deps.AmoRepo,
|
||||||
|
amoClient: deps.AmoClient,
|
||||||
|
redisRepo: deps.RedisRepo,
|
||||||
|
logger: deps.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *PostFields) Start(ctx context.Context) {
|
||||||
|
ticker := time.NewTicker(10 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
wc.processTask(ctx)
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *PostFields) processTask(ctx context.Context) {
|
||||||
|
dealsDataForUpdate, forRestoringMap, err := wc.redisRepo.FetchingDeals(ctx)
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error fetching deals for update in redis", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for token, dealsData := range dealsDataForUpdate {
|
||||||
|
errorCheckerMap, err := wc.sendForUpdate(ctx, token, dealsData)
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error updating deals fields in db", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
for dealID, _ := range errorCheckerMap {
|
||||||
|
restoringData := forRestoringMap[dealID]
|
||||||
|
err = wc.redisRepo.CachingDealToRedis(ctx, restoringData.SaveDeal)
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error restoring deal in redis", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = wc.redisRepo.CachingLeadFieldsToRedis(ctx, restoringData.SaveDeal.AnswerID, restoringData.LeadFields)
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error restoring deal fields in redis", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *PostFields) sendForUpdate(ctx context.Context, token string, dealsData []models.MappingDealsData) (map[int32]struct{}, error) {
|
||||||
|
errorCheckerMap := make(map[int32]struct{})
|
||||||
|
var subDomain string
|
||||||
|
var reqToUpdate []models.UpdateDealReq
|
||||||
|
for _, data := range dealsData {
|
||||||
|
subDomain = data.SubDomain
|
||||||
|
req := models.UpdateDealReq{
|
||||||
|
DealID: data.DealID,
|
||||||
|
CustomFieldsValues: data.LeadFields,
|
||||||
|
}
|
||||||
|
fmt.Println("AAAA", data.LeadFields)
|
||||||
|
reqToUpdate = append(reqToUpdate, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, errResp := wc.amoClient.UpdatingDeal(reqToUpdate, token, subDomain)
|
||||||
|
if errResp != nil {
|
||||||
|
// todo также логирование ошибки в тг
|
||||||
|
wc.logger.Error("error sendig request for update deal fields", zap.Error(errResp))
|
||||||
|
for _, data := range reqToUpdate {
|
||||||
|
errorCheckerMap[data.DealID] = struct{}{}
|
||||||
|
}
|
||||||
|
return errorCheckerMap, errResp
|
||||||
|
}
|
||||||
|
|
||||||
|
err := wc.updateDealStatus(ctx, DealStatus{
|
||||||
|
Resp: resp,
|
||||||
|
AccessToken: token,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error update deal Status after updating fields", zap.Error(err))
|
||||||
|
return errorCheckerMap, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorCheckerMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DealStatus struct {
|
||||||
|
Resp *models.UpdateDealResp
|
||||||
|
AccessToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *PostFields) updateDealStatus(ctx context.Context, deps DealStatus) error {
|
||||||
|
status := "success"
|
||||||
|
for _, lead := range deps.Resp.Embedded.Leads {
|
||||||
|
dealID := lead.ID
|
||||||
|
err := wc.amoRepo.AmoRepo.UpdatingDealAmoStatus(ctx, amo.SaveDealAmoDeps{DealID: dealID, AccessToken: deps.AccessToken, Status: status})
|
||||||
|
if err != nil {
|
||||||
|
wc.logger.Error("error saving deal status update to database", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *PostFields) Stop(_ context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
237
internal/workers/queueUpdater/queue_updater.go
Normal file
237
internal/workers/queueUpdater/queue_updater.go
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
package queueUpdater
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"amocrm/internal/workers_methods"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/twmb/franz-go/pkg/kgo"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"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(10 * 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.CheckPipelinesAndSteps(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
|
||||||
|
}
|
||||||
|
|
||||||
|
if token == nil {
|
||||||
|
return 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
|
||||||
|
}
|
||||||
|
|
||||||
|
err = wc.methods.CheckPipelinesAndSteps(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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 и их steps
|
||||||
|
err = wc.methods.CheckPipelinesAndSteps(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
|
||||||
|
}
|
78
internal/workers/tokens/tokens_updater.go
Normal file
78
internal/workers/tokens/tokens_updater.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package tokens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/pkg/amoClient"
|
||||||
|
"context"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
AmoClient *amoClient.Amo
|
||||||
|
Repo *dal.AmoDal
|
||||||
|
Logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
amoClient *amoClient.Amo
|
||||||
|
repo *dal.AmoDal
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRefreshWC(deps Deps) *Token {
|
||||||
|
return &Token{
|
||||||
|
amoClient: deps.AmoClient,
|
||||||
|
repo: deps.Repo,
|
||||||
|
logger: deps.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *Token) Start(ctx context.Context) {
|
||||||
|
ticker := time.NewTicker(5 * time.Minute)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
wc.processTasks(ctx)
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *Token) processTasks(ctx context.Context) {
|
||||||
|
//tokens, err := wc.repo.AmoRepo.CheckExpired(ctx)
|
||||||
|
//if err != nil {
|
||||||
|
// wc.logger.Error("error fetch expired tokens in mongo", zap.Error(err))
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
//for _, token := range tokens {
|
||||||
|
// req := models.UpdateWebHookReq{
|
||||||
|
// GrantType: "refresh_token",
|
||||||
|
// RefreshToken: token.RefreshToken,
|
||||||
|
// }
|
||||||
|
// newTokens, err := wc.amoClient.CreateWebHook(&req)
|
||||||
|
// if err != nil {
|
||||||
|
// wc.logger.Error("error create webhook for update tokens", zap.Error(err))
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// err = wc.repo.AmoRepo.WebhookUpdate(ctx, model.Token{
|
||||||
|
// AccountID: token.AccountID,
|
||||||
|
// RefreshToken: newTokens.RefreshToken,
|
||||||
|
// AccessToken: newTokens.AccessToken,
|
||||||
|
// Expiration: time.Now().Unix() + newTokens.ExpiresIn,
|
||||||
|
// CreatedAt: time.Now().Unix(),
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// wc.logger.Error("error update new tokens in mongo", zap.Error(err))
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *Token) Stop(_ context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
771
internal/workers_methods/methods.go
Normal file
771
internal/workers_methods/methods.go
Normal file
@ -0,0 +1,771 @@
|
|||||||
|
package workers_methods
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"amocrm/internal/tools"
|
||||||
|
"amocrm/pkg/amoClient"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"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.AmoDal
|
||||||
|
amoClient *amoClient.Amo
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
Repo *dal.AmoDal
|
||||||
|
AmoClient *amoClient.Amo
|
||||||
|
Logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWorkersMethods(deps Deps) *Methods {
|
||||||
|
return &Methods{
|
||||||
|
repo: deps.Repo,
|
||||||
|
amoClient: deps.AmoClient,
|
||||||
|
logger: deps.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Methods) UpdateTokens(ctx context.Context) ([]model.Token, error) {
|
||||||
|
allTokens, err := m.repo.AmoRepo.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 {
|
||||||
|
user, err := m.repo.AmoRepo.GetCurrentAccount(ctx, oldToken.AccountID)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error getting account by id in UpdateTokens", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req := models.UpdateWebHookReq{
|
||||||
|
GrantType: "refresh_token",
|
||||||
|
RefreshToken: oldToken.RefreshToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := m.amoClient.CreateWebHook(&req, user.Subdomain)
|
||||||
|
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.AmoRepo.WebhookUpdate(ctx, newToken)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error update token in db", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newTokens, err := m.repo.AmoRepo.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.Users)
|
||||||
|
for _, token := range allTokens {
|
||||||
|
user, err := m.repo.AmoRepo.GetCurrentAccount(ctx, token.AccountID)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error getting account by id in CheckUsers", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
page := 1
|
||||||
|
limit := 250
|
||||||
|
userData, err := m.amoClient.GetUserList(models.RequestGetListUsers{
|
||||||
|
Page: page,
|
||||||
|
Limit: limit,
|
||||||
|
}, token.AccessToken, user.Subdomain)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error fetching list users", zap.Error(err))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
listUser[token.AccountID] = append(listUser[token.AccountID], userData.Embedded.Users...)
|
||||||
|
}
|
||||||
|
|
||||||
|
for accountID, users := range listUser {
|
||||||
|
mainAccount, err := m.repo.AmoRepo.GetCurrentAccount(ctx, accountID)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error getting current account from db", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
currentUserUsers, err := m.repo.AmoRepo.GetUserUsersByID(ctx, mainAccount.AmoID)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error getting user users by amo user id", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
found := false
|
||||||
|
for _, currentUser := range currentUserUsers {
|
||||||
|
if user.ID == currentUser.AmoUserID {
|
||||||
|
found = true
|
||||||
|
err := m.repo.AmoRepo.UpdateAmoAccountUser(ctx, model.AmoAccountUser{
|
||||||
|
AmoID: currentUser.AmoID,
|
||||||
|
AmoUserID: currentUser.AmoUserID,
|
||||||
|
Name: user.Name,
|
||||||
|
Email: user.Email,
|
||||||
|
Role: int32(user.Rights.RoleID),
|
||||||
|
Group: int32(user.Rights.GroupID),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("failed update user amo account in db", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
err := m.repo.AmoRepo.AddAmoAccountUser(ctx, model.AmoAccountUser{
|
||||||
|
AmoID: mainAccount.AmoID,
|
||||||
|
AmoUserID: user.ID,
|
||||||
|
Name: user.Name,
|
||||||
|
Email: user.Email,
|
||||||
|
Role: int32(user.Rights.RoleID),
|
||||||
|
Group: int32(user.Rights.GroupID),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("failed insert user amo account in db", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var deletedUserIDs []int64
|
||||||
|
for _, currentUserUser := range currentUserUsers {
|
||||||
|
found := false
|
||||||
|
for _, user := range users {
|
||||||
|
if currentUserUser.AmoUserID == user.ID {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
deletedUserIDs = append(deletedUserIDs, currentUserUser.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(deletedUserIDs) > 0 {
|
||||||
|
err := m.repo.AmoRepo.DeleteUsers(ctx, deletedUserIDs)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error deleting users in db", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Methods) CheckPipelinesAndSteps(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
|
||||||
|
}
|
||||||
|
currentUserPipelines, err := m.repo.AmoRepo.GetUserPipelinesByID(ctx, user.AmoID)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error getting user pipelines by amo id", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
currentUserSteps, err := m.repo.AmoRepo.GetUserStepsByID(ctx, user.AmoID)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error getting user steps by amo id", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var receivedSteps []model.Step
|
||||||
|
|
||||||
|
pipelines, err := m.amoClient.GetListPipelines(token.AccessToken, user.Subdomain)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error fetching list pipelines from amo", zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(pipelines.Embedded.Pipelines) > 0 {
|
||||||
|
receivedPipelines := tools.ToPipeline(pipelines.Embedded.Pipelines)
|
||||||
|
err = m.repo.AmoRepo.CheckPipelines(ctx, receivedPipelines)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error update list pipelines in db:", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pipeline := range pipelines.Embedded.Pipelines {
|
||||||
|
steps, err := m.amoClient.GetListSteps(pipeline.ID, token.AccessToken, user.Subdomain)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error getting list steps pipeline:", zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedStep := tools.ToStep(steps.Embedded.Statuses)
|
||||||
|
receivedSteps = append(receivedSteps, receivedStep...)
|
||||||
|
err = m.repo.AmoRepo.CheckSteps(ctx, receivedStep)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error update pipeline steps in db:", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var deletedPipelineIDs []int64
|
||||||
|
for _, currentUserPipeline := range currentUserPipelines {
|
||||||
|
found := false
|
||||||
|
for _, receivedPipeline := range receivedPipelines {
|
||||||
|
if currentUserPipeline.Amoid == receivedPipeline.Amoid && currentUserPipeline.AccountID == user.AmoID {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
deletedPipelineIDs = append(deletedPipelineIDs, currentUserPipeline.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(deletedPipelineIDs) > 0 {
|
||||||
|
err := m.repo.AmoRepo.DeletePipelines(ctx, deletedPipelineIDs)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error deleting pipelines in db", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var deletedStepIDs []int64
|
||||||
|
for _, currentUserStep := range currentUserSteps {
|
||||||
|
found := false
|
||||||
|
for _, receivedStep := range receivedSteps {
|
||||||
|
if currentUserStep.Amoid == receivedStep.Amoid && currentUserStep.Accountid == user.AmoID && currentUserStep.Pipelineid == receivedStep.Pipelineid {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
deletedStepIDs = append(deletedStepIDs, currentUserStep.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(deletedStepIDs) > 0 {
|
||||||
|
err := m.repo.AmoRepo.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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
currentUserFields, err := m.repo.AmoRepo.GetUserFieldsByID(ctx, user.AmoID)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error getting user fields by amo id", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(4)
|
||||||
|
|
||||||
|
var fieldsMap 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 := 50
|
||||||
|
|
||||||
|
for {
|
||||||
|
req := models.GetListFieldsReq{
|
||||||
|
Page: page,
|
||||||
|
Limit: limit,
|
||||||
|
EntityType: entityType,
|
||||||
|
}
|
||||||
|
fields, err := m.amoClient.GetListFields(req, token.AccessToken, user.Subdomain)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error getting list of fields", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if fields == nil || len(fields.Embedded.CustomFields) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldsMap.Store(entityType, fields.Embedded.CustomFields)
|
||||||
|
|
||||||
|
page++
|
||||||
|
}
|
||||||
|
}(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.CustomField)) > 0 {
|
||||||
|
receivedFields := tools.ToField(fields.([]models.CustomField), entityType)
|
||||||
|
for _, field := range receivedFields {
|
||||||
|
if currentUserField.Amoid == field.Amoid && currentUserField.Accountid == user.AmoID && currentUserField.Entity == entityType {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
deletedFieldIDs = append(deletedFieldIDs, currentUserField.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(deletedFieldIDs) > 0 {
|
||||||
|
err = m.repo.AmoRepo.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.CustomField)) > 0 {
|
||||||
|
err := m.repo.AmoRepo.CheckFields(ctx, tools.ToField(fields.([]models.CustomField), entityType), token.AccountID)
|
||||||
|
if err != nil {
|
||||||
|
switch entityType {
|
||||||
|
case model.LeadsType:
|
||||||
|
m.logger.Error("error updating leads fields in db", zap.Error(err))
|
||||||
|
return err
|
||||||
|
case model.ContactsType:
|
||||||
|
m.logger.Error("error updating contacts fields in db", zap.Error(err))
|
||||||
|
return err
|
||||||
|
case model.CompaniesType:
|
||||||
|
m.logger.Error("error updating companies fields in db", zap.Error(err))
|
||||||
|
return err
|
||||||
|
case model.CustomersType:
|
||||||
|
m.logger.Error("error updating customer 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.AmoRepo.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) {
|
||||||
|
// получаем аксес и рефреш токены по коду авторизации
|
||||||
|
forGetTokens := models.CreateWebHookReq{
|
||||||
|
GrantType: "authorization_code",
|
||||||
|
Code: msg.AuthCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens, err := m.amoClient.CreateWebHook(&forGetTokens, msg.RefererURL)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error getting webhook in CreateUserFromWebHook:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// получаем информацию о пользователе по аксес токену
|
||||||
|
userInfo, err := m.amoClient.GetUserInfo(tokens.AccessToken, msg.RefererURL)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error getting UserInfo in CreateUserFromWebHook:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
toCreate := model.AmoAccount{
|
||||||
|
AccountID: msg.AccountID,
|
||||||
|
AmoID: userInfo.ID,
|
||||||
|
Name: userInfo.Name,
|
||||||
|
Subdomain: msg.RefererURL,
|
||||||
|
Country: userInfo.Country,
|
||||||
|
DriveURL: userInfo.DriveUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = m.repo.AmoRepo.CreateAccount(ctx, toCreate)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error create account in db in CreateUserFromWebHook", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = m.repo.AmoRepo.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 nil, 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, customerIDs, contactIDs []int32
|
||||||
|
leadQuestions, companyQuestions, customerQuestions, contactQuestions []model.Question
|
||||||
|
questionsTypeMap = make(map[model.EntityType][]model.Question)
|
||||||
|
newFields []model.Field
|
||||||
|
lead, company, customer, contact model.FieldRule
|
||||||
|
currentFieldsRule = msg.Rule.Fieldsrule
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
user, err := m.repo.AmoRepo.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.AmoRepo.GetUserFieldsByID(ctx, user.AmoID)
|
||||||
|
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)
|
||||||
|
customerIDs = tools.ToQuestionIDs(msg.Rule.Fieldsrule.Customer.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(customerIDs, &customerQuestions)
|
||||||
|
getQuestions(companyIDs, &companyQuestions)
|
||||||
|
getQuestions(contactIDs, &contactQuestions)
|
||||||
|
|
||||||
|
questionsTypeMap[model.LeadsType] = append(questionsTypeMap[model.LeadsType], leadQuestions...)
|
||||||
|
questionsTypeMap[model.CustomersType] = append(questionsTypeMap[model.CustomersType], customerQuestions...)
|
||||||
|
questionsTypeMap[model.CompaniesType] = append(questionsTypeMap[model.CompaniesType], companyQuestions...)
|
||||||
|
questionsTypeMap[model.ContactsType] = append(questionsTypeMap[model.ContactsType], contactQuestions...)
|
||||||
|
|
||||||
|
toCreated, toUpdate := tools.ToCreatedUpdateQuestionRules(questionsTypeMap, currentFields)
|
||||||
|
contactFieldsToCreate, forAdding := tools.ForContactRules(quizConfig, currentFields)
|
||||||
|
|
||||||
|
for entity, fields := range toCreated {
|
||||||
|
if len(fields) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
createdFields, err := m.amoClient.AddFields(fields, entity, token, user.Subdomain)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error adding fields to amo", zap.Any("type", entity), zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newFields = append(newFields, tools.ToField(createdFields.Embedded.CustomFields, entity)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(contactFieldsToCreate) > 0 {
|
||||||
|
createdFields, err := m.amoClient.AddFields(contactFieldsToCreate, model.ContactsType, token, user.Subdomain)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error adding fields to amo", zap.Any("type", model.ContactsType), zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
contructedFields := tools.ToField(createdFields.Embedded.CustomFields, model.ContactsType)
|
||||||
|
|
||||||
|
newFields = append(newFields, contructedFields...)
|
||||||
|
|
||||||
|
for _, field := range contructedFields {
|
||||||
|
if _, ok := forAdding[field.Name]; ok {
|
||||||
|
forAdding[field.Name] = int(field.Amoid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newFields) > 0 {
|
||||||
|
err = m.repo.AmoRepo.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]int, questions []model.Question, fieldRule *model.FieldRule, currentEntity model.EntityType) {
|
||||||
|
ruleMap := make(map[int]int)
|
||||||
|
for questionID, fieldID := range fieldRuleArrCurrent {
|
||||||
|
if fieldID != 0 {
|
||||||
|
// если 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.Name, " ", ""))
|
||||||
|
if title == fieldName && field.Entity == currentEntity {
|
||||||
|
ruleMap[questionID] = int(field.Amoid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fieldRule.Questionid = ruleMap
|
||||||
|
}
|
||||||
|
|
||||||
|
constructFieldRules(currentFieldsRule.Lead.Questionid, leadQuestions, &lead, model.LeadsType)
|
||||||
|
constructFieldRules(currentFieldsRule.Customer.Questionid, customerQuestions, &customer, model.CustomersType)
|
||||||
|
constructFieldRules(currentFieldsRule.Company.Questionid, companyQuestions, &company, model.CompaniesType)
|
||||||
|
constructFieldRules(currentFieldsRule.Contact.Questionid, contactQuestions, &contact, model.ContactsType)
|
||||||
|
|
||||||
|
err = m.repo.AmoRepo.UpdateFieldRules(ctx, model.Fieldsrule{
|
||||||
|
Lead: lead,
|
||||||
|
Customer: customer,
|
||||||
|
Company: company,
|
||||||
|
Contact: model.ContactRules{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.amoClient.CreateWebHook(&forGetTokens, msg.RefererURL)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error getting tokens in method user re-login:", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo, err := m.amoClient.GetUserInfo(tokens.AccessToken, msg.RefererURL)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("error getting UserInfo in method user re-login:", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
toUpdate := model.AmoAccount{
|
||||||
|
AccountID: msg.AccountID,
|
||||||
|
AmoID: userInfo.ID,
|
||||||
|
Name: userInfo.Name,
|
||||||
|
Subdomain: msg.RefererURL,
|
||||||
|
Country: userInfo.Country,
|
||||||
|
DriveURL: userInfo.DriveUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = m.repo.AmoRepo.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.AmoRepo.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
|
||||||
|
}
|
912
openapi.yaml
912
openapi.yaml
File diff suppressed because it is too large
Load Diff
846
pkg/amoClient/amo.go
Normal file
846
pkg/amoClient/amo.go
Normal file
@ -0,0 +1,846 @@
|
|||||||
|
package amoClient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"amocrm/internal/workers/limiter"
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Amo struct {
|
||||||
|
fiberClient *fiber.Client
|
||||||
|
logger *zap.Logger
|
||||||
|
redirectionURL string
|
||||||
|
integrationID string
|
||||||
|
integrationSecret string
|
||||||
|
rateLimiter *limiter.RateLimiter
|
||||||
|
fileMutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type AmoDeps struct {
|
||||||
|
FiberClient *fiber.Client
|
||||||
|
Logger *zap.Logger
|
||||||
|
RedirectionURL string
|
||||||
|
IntegrationID string
|
||||||
|
IntegrationSecret string
|
||||||
|
RateLimiter *limiter.RateLimiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAmoClient(deps AmoDeps) *Amo {
|
||||||
|
if deps.FiberClient == nil {
|
||||||
|
deps.FiberClient = fiber.AcquireClient()
|
||||||
|
}
|
||||||
|
return &Amo{
|
||||||
|
fiberClient: deps.FiberClient,
|
||||||
|
logger: deps.Logger,
|
||||||
|
redirectionURL: deps.RedirectionURL,
|
||||||
|
integrationSecret: deps.IntegrationSecret,
|
||||||
|
integrationID: deps.IntegrationID,
|
||||||
|
rateLimiter: deps.RateLimiter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// токен должен быть с правами администратора
|
||||||
|
// https://www.amocrm.ru/developers/content/crm_platform/users-api#users-list
|
||||||
|
func (a *Amo) GetUserList(req models.RequestGetListUsers, accesToken string, domain string) (*models.ResponseGetListUsers, error) {
|
||||||
|
for {
|
||||||
|
if a.rateLimiter.Check() {
|
||||||
|
uri := fmt.Sprintf("https://%s/ajax/v3/users?with=rights&page=%d&limit=%d", domain, req.Page, req.Limit)
|
||||||
|
|
||||||
|
agent := a.fiberClient.Get(uri)
|
||||||
|
agent.Set("Authorization", "Bearer "+accesToken)
|
||||||
|
|
||||||
|
statusCode, resBody, errs := agent.Bytes()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for _, err := range errs {
|
||||||
|
a.logger.Error("error sending request in GetUserList", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request GetUserList failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != fiber.StatusOK {
|
||||||
|
switch statusCode {
|
||||||
|
case fiber.StatusForbidden:
|
||||||
|
errorMessage := fmt.Sprintf("error GetUserList StatusForbidden - %d", statusCode)
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
case fiber.StatusUnauthorized:
|
||||||
|
errorMessage := fmt.Sprintf("error GetUserList StatusUnauthorized - %d", statusCode)
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
default:
|
||||||
|
errorMessage := fmt.Sprintf("error GetUserList statusCode - %d", statusCode)
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var userListResponse models.ResponseGetListUsers
|
||||||
|
err := json.Unmarshal(resBody, &userListResponse)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal ResponseGetListUsers:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &userListResponse, nil
|
||||||
|
}
|
||||||
|
time.Sleep(a.rateLimiter.Interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.amocrm.ru/developers/content/oauth/step-by-step
|
||||||
|
// POST /oauth2/access_token
|
||||||
|
// тут и создание по коду и обновление по рефрешу в этом клиенте
|
||||||
|
func (a *Amo) CreateWebHook(req models.WebHookRequest, domain string) (*models.CreateWebHookResp, error) {
|
||||||
|
for {
|
||||||
|
if a.rateLimiter.Check() {
|
||||||
|
req.SetClientID(a.integrationID)
|
||||||
|
req.SetClientSecret(a.integrationSecret)
|
||||||
|
req.SetRedirectURL(a.redirectionURL)
|
||||||
|
bodyBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error marshal req in CreateWebHook:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
agent := a.fiberClient.Post("https://" + domain + "/oauth2/access_token")
|
||||||
|
agent.Set("Content-Type", "application/json").Body(bodyBytes)
|
||||||
|
|
||||||
|
statusCode, resBody, errs := agent.Bytes()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for _, err = range errs {
|
||||||
|
a.logger.Error("error sending request in CreateWebHook for create or update tokens", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != fiber.StatusOK {
|
||||||
|
errorMessage := fmt.Sprintf("received an incorrect response from CreateWebHook: %s", string(resBody))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokens models.CreateWebHookResp
|
||||||
|
err = json.Unmarshal(resBody, &tokens)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal CreateWebHookResp:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tokens, nil
|
||||||
|
}
|
||||||
|
time.Sleep(a.rateLimiter.Interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.amocrm.ru/developers/content/crm_platform/leads_pipelines#%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA-%D1%81%D1%82%D0%B0%D1%82%D1%83%D1%81%D0%BE%D0%B2-%D0%B2%D0%BE%D1%80%D0%BE%D0%BD%D0%BA%D0%B8-%D1%81%D0%B4%D0%B5%D0%BB%D0%BE%D0%BA
|
||||||
|
// GET /api/v4/leads/pipelines/{pipeline_id}/statuses
|
||||||
|
func (a *Amo) GetListSteps(pipelineID int, accessToken string, domain string) (*models.ResponseGetListSteps, error) {
|
||||||
|
for {
|
||||||
|
if a.rateLimiter.Check() {
|
||||||
|
uri := fmt.Sprintf("https://%s/api/v4/leads/pipelines/%d/statuses", domain, pipelineID)
|
||||||
|
agent := a.fiberClient.Get(uri)
|
||||||
|
agent.Set("Authorization", "Bearer "+accessToken)
|
||||||
|
statusCode, resBody, errs := agent.Bytes()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for _, err := range errs {
|
||||||
|
a.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("received an incorrect response from GetListSteps: %s", string(resBody))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var listSteps models.ResponseGetListSteps
|
||||||
|
err := json.Unmarshal(resBody, &listSteps)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal ResponseGetListSteps:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &listSteps, nil
|
||||||
|
}
|
||||||
|
time.Sleep(a.rateLimiter.Interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.amocrm.ru/developers/content/crm_platform/custom-fields#%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA-%D0%BF%D0%BE%D0%BB%D0%B5%D0%B9-%D1%81%D1%83%D1%89%D0%BD%D0%BE%D1%81%D1%82%D0%B8
|
||||||
|
// GET /api/v4/leads/custom_fields
|
||||||
|
// GET /api/v4/contacts/custom_fields
|
||||||
|
// GET /api/v4/companies/custom_fields
|
||||||
|
// GET /api/v4/customers/custom_fields
|
||||||
|
|
||||||
|
// пока без этих двух
|
||||||
|
// GET /api/v4/customers/segments/custom_fields
|
||||||
|
// GET /api/v4/catalogs/{catalog_id}/custom_fields
|
||||||
|
// эти методы все относятся к одному и тому же, поэтому на вход будет урл и рек стуктура, выход у них один и тот же
|
||||||
|
func (a *Amo) GetListFields(req models.GetListFieldsReq, accessToken string, domain string) (*models.ResponseGetListFields, error) {
|
||||||
|
for {
|
||||||
|
if a.rateLimiter.Check() {
|
||||||
|
fullURL := fmt.Sprintf("https://%s/api/v4/%s/custom_fields?limit=%d&page=%d", domain, req.EntityType, req.Limit, req.Page)
|
||||||
|
agent := a.fiberClient.Get(fullURL)
|
||||||
|
agent.Set("Authorization", "Bearer "+accessToken)
|
||||||
|
statusCode, resBody, errs := agent.Bytes()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for _, err := range errs {
|
||||||
|
a.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))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var listFields models.ResponseGetListFields
|
||||||
|
err := json.Unmarshal(resBody, &listFields)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal ResponseGetListFields:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &listFields, nil
|
||||||
|
}
|
||||||
|
time.Sleep(a.rateLimiter.Interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.amocrm.ru/developers/content/crm_platform/tags-api#%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA-%D1%82%D0%B5%D0%B3%D0%BE%D0%B2-%D0%B4%D0%BB%D1%8F-%D1%81%D1%83%D1%89%D0%BD%D0%BE%D1%81%D1%82%D0%B8
|
||||||
|
// GET /api/v4/{entity_type:leads|contacts|companies|customers}/tags
|
||||||
|
func (a *Amo) GetListTags(req models.GetListTagsReq, accessToken string, domain string) (*models.ResponseGetListTags, error) {
|
||||||
|
for {
|
||||||
|
if a.rateLimiter.Check() {
|
||||||
|
fullURL := fmt.Sprintf("https://%s/api/v4/%s/tags?", domain, req.EntityType)
|
||||||
|
|
||||||
|
if req.Filter.Name != "" {
|
||||||
|
fullURL += "&filter[name]=" + url.QueryEscape(req.Filter.Name)
|
||||||
|
}
|
||||||
|
if len(req.Filter.ID) > 0 {
|
||||||
|
for _, id := range req.Filter.ID {
|
||||||
|
fullURL += fmt.Sprintf("&filter[id][]=%d", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.Filter.Query != "" {
|
||||||
|
fullURL += "&filter[query]=" + url.QueryEscape(req.Filter.Query)
|
||||||
|
}
|
||||||
|
|
||||||
|
fullURL += fmt.Sprintf("&page=%d&limit=%d", req.Page, req.Limit)
|
||||||
|
|
||||||
|
agent := a.fiberClient.Get(fullURL)
|
||||||
|
agent.Set("Authorization", "Bearer "+accessToken)
|
||||||
|
statusCode, resBody, errs := agent.Bytes()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for _, err := range errs {
|
||||||
|
a.logger.Error("error sending request in GetListTags", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request GetListTags failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != fiber.StatusOK {
|
||||||
|
errorMessage := fmt.Sprintf("received an incorrect response from GetListTags: %s", string(resBody))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var listTags models.ResponseGetListTags
|
||||||
|
err := json.Unmarshal(resBody, &listTags)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal ResponseGetListTags:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &listTags, nil
|
||||||
|
}
|
||||||
|
time.Sleep(a.rateLimiter.Interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.amocrm.ru/developers/content/crm_platform/account-info
|
||||||
|
// GET /api/v4/account
|
||||||
|
func (a *Amo) GetUserInfo(accessToken string, domain string) (*models.AmocrmUserInformation, error) {
|
||||||
|
for {
|
||||||
|
if a.rateLimiter.Check() {
|
||||||
|
url := fmt.Sprintf("https://%s/api/v4/account?with=drive_url", domain)
|
||||||
|
agent := a.fiberClient.Get(url)
|
||||||
|
agent.Set("Authorization", "Bearer "+accessToken)
|
||||||
|
statusCode, resBody, errs := agent.Bytes()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for _, err := range errs {
|
||||||
|
a.logger.Error("error sending request in GetUserInfo", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request GetUserInfo failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != fiber.StatusOK {
|
||||||
|
errorMessage := fmt.Sprintf("received an incorrect response from GetUserInfo: %s", string(resBody))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userInfo models.AmocrmUserInformation
|
||||||
|
err := json.Unmarshal(resBody, &userInfo)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal AmocrmUserInformation:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
||||||
|
time.Sleep(a.rateLimiter.Interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.amocrm.ru/developers/content/crm_platform/leads_pipelines#%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA-%D0%B2%D0%BE%D1%80%D0%BE%D0%BD%D0%BE%D0%BA-%D1%81%D0%B4%D0%B5%D0%BB%D0%BE%D0%BA
|
||||||
|
// GET /api/v4/leads/pipelines
|
||||||
|
func (a *Amo) GetListPipelines(accessToken string, domain string) (*models.PipelineResponse, error) {
|
||||||
|
for {
|
||||||
|
if a.rateLimiter.Check() {
|
||||||
|
uri := fmt.Sprintf("https://%s/api/v4/leads/pipelines", domain)
|
||||||
|
agent := a.fiberClient.Get(uri)
|
||||||
|
agent.Set("Authorization", "Bearer "+accessToken)
|
||||||
|
statusCode, resBody, errs := agent.Bytes()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for _, err := range errs {
|
||||||
|
a.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("received an incorrect response from GetListPipelines: %s", string(resBody))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userInfo models.PipelineResponse
|
||||||
|
err := json.Unmarshal(resBody, &userInfo)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal PipelineResponse:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
||||||
|
time.Sleep(a.rateLimiter.Interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// токен должен быть с правами администратора
|
||||||
|
// https://www.amocrm.ru/developers/content/crm_platform/users-api#user-detail
|
||||||
|
// GET /api/v4/users/{id
|
||||||
|
func (a *Amo) GetUserByID(id int32, accessToken string, domain string) (*models.OneUserInfo, error) {
|
||||||
|
for {
|
||||||
|
if a.rateLimiter.Check() {
|
||||||
|
uri := fmt.Sprintf("https://%s/api/v4/users/%d?with=role,uuid", domain, id)
|
||||||
|
agent := a.fiberClient.Get(uri)
|
||||||
|
agent.Set("Authorization", "Bearer "+accessToken)
|
||||||
|
statusCode, resBody, errs := agent.Bytes()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for _, err := range errs {
|
||||||
|
a.logger.Error("error sending request in GetUserByID", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request GetUserByID failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != fiber.StatusOK {
|
||||||
|
errorMessage := fmt.Sprintf("received an incorrect response from Get User By ID:%s", string(resBody))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userInfo models.OneUserInfo
|
||||||
|
err := json.Unmarshal(resBody, &userInfo)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal response body in Get User By ID:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
||||||
|
time.Sleep(a.rateLimiter.Interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Amo) AddFields(req []models.AddLeadsFields, entity model.EntityType, accessToken string, domain string) (*models.ResponseGetListFields, error) {
|
||||||
|
for {
|
||||||
|
if a.rateLimiter.Check() {
|
||||||
|
uri := fmt.Sprintf("https://%s/api/v4/%s/custom_fields", domain, entity)
|
||||||
|
bodyBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error marshal req in Add Fields:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
agent := a.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 {
|
||||||
|
a.logger.Error("error sending request in Add Fields for add fields", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != fiber.StatusOK {
|
||||||
|
errorMessage := fmt.Sprintf("received an incorrect response from Add Fields: %s", string(resBody))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var fields models.ResponseGetListFields
|
||||||
|
err = json.Unmarshal(resBody, &fields)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal response body in Add Fields:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &fields, nil
|
||||||
|
}
|
||||||
|
time.Sleep(a.rateLimiter.Interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Amo) CreatingDeal(req []models.DealReq, accessToken string, domain string) ([]models.DealResp, error) {
|
||||||
|
for {
|
||||||
|
if a.rateLimiter.Check() {
|
||||||
|
uri := fmt.Sprintf("https://%s/api/v4/leads/complex", domain)
|
||||||
|
bodyBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error marshal req in Creating Deal:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
agent := a.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 {
|
||||||
|
a.logger.Error("error sending request in Creating Deal for creating deals", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != fiber.StatusOK {
|
||||||
|
errorMessage := fmt.Sprintf("received an incorrect response from Creating Deal: %s", string(resBody))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp []models.DealResp
|
||||||
|
err = json.Unmarshal(resBody, &resp)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal response body in Creating Deal:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
time.Sleep(a.rateLimiter.Interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Amo) UpdatingDeal(req []models.UpdateDealReq, accessToken string, domain string) (*models.UpdateDealResp, error) {
|
||||||
|
for {
|
||||||
|
if a.rateLimiter.Check() {
|
||||||
|
uri := fmt.Sprintf("https://%s/api/v4/leads", domain)
|
||||||
|
bodyBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error marshal req in Updating Deal:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
agent := a.fiberClient.Patch(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 {
|
||||||
|
a.logger.Error("error sending request in Updating Deal for updating deals", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != fiber.StatusOK {
|
||||||
|
errorMessage := fmt.Sprintf("received an incorrect response from Updating Deal: %s", string(resBody))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode), zap.String("domain", domain), zap.String("token", accessToken))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp models.UpdateDealResp
|
||||||
|
err = json.Unmarshal(resBody, &resp)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal response body in Updating Deal:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
time.Sleep(a.rateLimiter.Interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Amo) CreatingCustomer(req []models.Customer, accessToken string, domain string) (*models.CustomerResp, error) {
|
||||||
|
for {
|
||||||
|
if a.rateLimiter.Check() {
|
||||||
|
uri := fmt.Sprintf("https://%s/api/v4/customers", domain)
|
||||||
|
bodyBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error marshal req in Creating Customer:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
agent := a.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 {
|
||||||
|
a.logger.Error("error sending request in Creating Customer for creating customers", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != fiber.StatusOK {
|
||||||
|
errorMessage := fmt.Sprintf("received an incorrect response from Creating Customer: %s", string(resBody))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp models.CustomerResp
|
||||||
|
err = json.Unmarshal(resBody, &resp)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal response body in Creating Customer:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
time.Sleep(a.rateLimiter.Interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo подумать на счет хранилища в амо
|
||||||
|
func (a *Amo) downloadFile(urlFile string) (*os.File, error) {
|
||||||
|
var err error
|
||||||
|
agent := a.fiberClient.Get(urlFile)
|
||||||
|
|
||||||
|
statusCode, resBody, errs := agent.Bytes()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for _, err = range errs {
|
||||||
|
a.logger.Error("error sending request for getting file by url", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, 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))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := strings.Split(urlFile, "/")
|
||||||
|
tmpFile, err := os.Create(fileName[len(fileName)-1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(tmpFile, bytes.NewReader(resBody))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Amo) UploadFileToAmo(urlFile string, accessToken string, driveURL string) (*models.ValuesFile, error) {
|
||||||
|
a.fileMutex.Lock()
|
||||||
|
defer a.fileMutex.Unlock()
|
||||||
|
localFile, err := a.downloadFile(urlFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.Remove(localFile.Name())
|
||||||
|
|
||||||
|
fileInfo, err := os.Stat(localFile.Name())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSize := fileInfo.Size()
|
||||||
|
createSessionData := &models.CreateSession{
|
||||||
|
FileName: localFile.Name(),
|
||||||
|
FileSize: fileSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("%s/v1.0/sessions", driveURL)
|
||||||
|
bodyBytes, err := json.Marshal(createSessionData)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error marshal create session data:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
agent := a.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 {
|
||||||
|
a.logger.Error("error sending request to create session for upload file in amo", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != fiber.StatusOK {
|
||||||
|
errorMessage := fmt.Sprintf("received an incorrect response from creating upload file session: %s", string(resBody))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp models.UploadSession
|
||||||
|
err = json.Unmarshal(resBody, &resp)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal response body in creating upload file session:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := a.createPart(resp, localFile)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error create part file sending to amo:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.ValuesFile{
|
||||||
|
Value: models.ValueFile{
|
||||||
|
FileUUID: response.UUID,
|
||||||
|
VersionUUID: response.VersionUUID,
|
||||||
|
FileName: response.Name,
|
||||||
|
FileSize: response.Size,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Amo) createPart(uploadData models.UploadSession, file *os.File) (*models.UploadedFile, error) {
|
||||||
|
defer file.Close()
|
||||||
|
fileInfo, err := file.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSize := fileInfo.Size()
|
||||||
|
|
||||||
|
var uploadedFile models.UploadedFile
|
||||||
|
maxSize := uploadData.MaxPartSize
|
||||||
|
var remainingSize = fileSize
|
||||||
|
var start int64 = 0
|
||||||
|
|
||||||
|
for remainingSize > 0 {
|
||||||
|
end := start + maxSize
|
||||||
|
if end > fileSize {
|
||||||
|
end = fileSize
|
||||||
|
}
|
||||||
|
|
||||||
|
partSize := end - start
|
||||||
|
|
||||||
|
partFile, err := os.OpenFile(file.Name(), os.O_RDONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer partFile.Close()
|
||||||
|
|
||||||
|
_, err = partFile.Seek(start, io.SeekStart)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := make([]byte, partSize)
|
||||||
|
_, err = partFile.Read(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
agent := a.fiberClient.Post(uploadData.UploadURL).Body(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode, resBody, errs := agent.Bytes()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for _, err = range errs {
|
||||||
|
a.logger.Error("error sending request to upload part file to amo", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != http.StatusOK && statusCode != http.StatusAccepted {
|
||||||
|
return nil, fmt.Errorf("failed to upload part file to amo, status: %d, respBody: %s", statusCode, string(resBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
start = end
|
||||||
|
remainingSize -= partSize
|
||||||
|
|
||||||
|
if statusCode == http.StatusAccepted {
|
||||||
|
var next struct {
|
||||||
|
NextUrl string `json:"next_url"`
|
||||||
|
SessionID int `json:"session_id"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(resBody, &next); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
uploadData.UploadURL = next.NextUrl
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(resBody, &uploadedFile); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &uploadedFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Amo) CreateContact(req []models.CreateContactReq, domain, accessToken string) (*models.ContactResponse, error) {
|
||||||
|
for {
|
||||||
|
if a.rateLimiter.Check() {
|
||||||
|
uri := fmt.Sprintf("https://%s/api/v4/contacts", domain)
|
||||||
|
bodyBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error marshal req in Creating Contact:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
agent := a.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 {
|
||||||
|
a.logger.Error("error sending request in Creating Contact", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != fiber.StatusOK {
|
||||||
|
errorMessage := fmt.Sprintf("received an incorrect response from Creating Contact: %s", string(resBody))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp models.ContactResponse
|
||||||
|
err = json.Unmarshal(resBody, &resp)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal response body in Creating Contact:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
time.Sleep(a.rateLimiter.Interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Amo) UpdateContact(req models.CreateContactReq, domain, accessToken string, idContact int32) (*models.ContactResponse, error) {
|
||||||
|
for {
|
||||||
|
if a.rateLimiter.Check() {
|
||||||
|
uri := fmt.Sprintf("https://%s/api/v4/contacts/%d", domain, idContact)
|
||||||
|
bodyBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error marshal req in Update Contact:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
agent := a.fiberClient.Patch(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 {
|
||||||
|
a.logger.Error("error sending request in Update Contact", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != fiber.StatusOK {
|
||||||
|
errorMessage := fmt.Sprintf("received an incorrect response from Update Contact: %s", string(resBody))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp models.ContactResponse
|
||||||
|
err = json.Unmarshal(resBody, &resp)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal response body in Update Contact:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
time.Sleep(a.rateLimiter.Interval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Amo) LinkedContactToContact(req []models.LinkedContactReq, domain, accessToken string, id int32) (*models.LinkedContactResponse, error) {
|
||||||
|
for {
|
||||||
|
if a.rateLimiter.Check() {
|
||||||
|
uri := fmt.Sprintf("https://%s/api/v4/contacts/%d/link", domain, id)
|
||||||
|
bodyBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error marshal req in Linked Contact To Contact:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
agent := a.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 {
|
||||||
|
a.logger.Error("error sending request in Linked Contact To Contact", zap.Error(err))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("request failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != fiber.StatusOK {
|
||||||
|
errorMessage := fmt.Sprintf("received an incorrect response from Linked Contact To Contact: %s", string(resBody))
|
||||||
|
a.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return nil, fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp models.LinkedContactResponse
|
||||||
|
err = json.Unmarshal(resBody, &resp)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error("error unmarshal response body in Linked Contact To Contact:", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
time.Sleep(a.rateLimiter.Interval)
|
||||||
|
}
|
||||||
|
}
|
125
pkg/amoClient/amo_test.go
Normal file
125
pkg/amoClient/amo_test.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
package amoClient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"amocrm/internal/workers/limiter"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_CreateWebhook(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
cfgLogger := zap.NewDevelopmentConfig()
|
||||||
|
cfgLogger.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||||
|
cfgLogger.EncoderConfig.ConsoleSeparator = " "
|
||||||
|
|
||||||
|
logger, err := cfgLogger.Build()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rateLimiter := limiter.NewRateLimiter(ctx, 6, 1500*time.Millisecond)
|
||||||
|
|
||||||
|
amoclient := NewAmoClient(AmoDeps{
|
||||||
|
Logger: logger,
|
||||||
|
RedirectionURL: "https://squiz.pena.digital/squiz/amocrm/oauth",
|
||||||
|
IntegrationID: "2dbd6329-9be6-41f2-aa5f-964b9e723e49",
|
||||||
|
IntegrationSecret: "tNK3LwL4ovP0OBK4jKDHJ3646PqRJDOKQYgY6P2t6DCuV8LEzDzszTDY0Fhwmzc8",
|
||||||
|
RateLimiter: rateLimiter,
|
||||||
|
})
|
||||||
|
|
||||||
|
req2 := models.CreateWebHookReq{
|
||||||
|
GrantType: "authorization_code",
|
||||||
|
Code: "def502003378c2a850f6f3b9618ee811a371e0552692060dd206e166244d8508f1df34e4870e12d183777d29e275fc2e1a5680f27f3999715be63a429a40bef4980ed28f03989b2acc90ac4f8a7a11b514a246a564170d0349ea1ec6584ba8f636ad0d856d6e9ed75e472d461ceee40052513335b9738d5782570a75ec7b4cb3c9bfcf564d93a30e548cff96c789b6097f5c4e254139dc829083ccc5395c276e1b29cd001b8f0efa5579b9e989caeaeb895a6602d70254715b969aa5ce8cd91fc379b406877f3d3258702c4f1f8ca6c8b52eed492aec209418801626e50a1b9b04f4346de452f20e7b4d9611a58e8742342481234a161662e35340aba3aedcb1616fac4be6a125fc6d2aa25ab04eed394ed3ee8f9749ed32048ce69a932f83cdd1fe4d8788ac28683698b729b7d4c36ba6a045d3dc488f5da968ddc4837bdb6a26d4e3f5abcb58c8175c3ab20c6c3bad13c613c77ef23484c2a1ebd4a2152168b15f8d21feafa3178cececdbd47f91863d715f5905b0385efa0744692d863a768aa431b07ea667fef134d3c3a749efdc064d74887a889219e68fd34ab435eb761fea6415f4c4760dd8887b8978d62a35e745826edac41019539012592f737ed5ca690b72ce06c7a2486847b95d47a157f0965eaad4839fd7d1927c03c6152f438dd92a465f58e753523965ac127abd7354",
|
||||||
|
}
|
||||||
|
resp, err := amoclient.CreateWebHook(&req2, "penadigitaltech.amocrm.ru")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_CustomersMode(t *testing.T) {
|
||||||
|
fiberClient := fiber.AcquireClient()
|
||||||
|
uri := fmt.Sprint("https://penadigitaltech.amocrm.ru/api/v4/customers/mode")
|
||||||
|
bodyBytes, err := json.Marshal(struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
ISenabled bool `json:"is_enabled"`
|
||||||
|
}{
|
||||||
|
Mode: "segments",
|
||||||
|
ISenabled: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
agent := fiberClient.Patch(uri)
|
||||||
|
agent.Set("Content-Type", "application/json").Body(bodyBytes)
|
||||||
|
agent.Set("Authorization", "Bearer "+"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjFkMmZlNjFlMjFkZGZjOGRmN2MyNWRhYjc3MTJkMDM5MDVhZWEwYzkyYjJmZTU0Njc4OTgzYmUzM2Q0NWExYjQ2YmI1ZDY2MjJkNDA2Y2ZjIn0.eyJhdWQiOiIyZGJkNjMyOS05YmU2LTQxZjItYWE1Zi05NjRiOWU3MjNlNDkiLCJqdGkiOiIxZDJmZTYxZTIxZGRmYzhkZjdjMjVkYWI3NzEyZDAzOTA1YWVhMGM5MmIyZmU1NDY3ODk4M2JlMzNkNDVhMWI0NmJiNWQ2NjIyZDQwNmNmYyIsImlhdCI6MTcxNDY0NDA3OSwibmJmIjoxNzE0NjQ0MDc5LCJleHAiOjE3MTQ3MzA0NzksInN1YiI6IjgxMTA3MjYiLCJncmFudF90eXBlIjoiIiwiYWNjb3VudF9pZCI6MzAyMjg5OTcsImJhc2VfZG9tYWluIjoiYW1vY3JtLnJ1IiwidmVyc2lvbiI6Miwic2NvcGVzIjpbInB1c2hfbm90aWZpY2F0aW9ucyIsImNybSIsIm5vdGlmaWNhdGlvbnMiXSwiaGFzaF91dWlkIjoiZWUyNjFjMDgtOGZiMC00NTkzLTlmNmQtOWVhNDFhZTljNTJhIn0.qUaJwH95LnU3xMA4GMQ0wtK1_vA_bMM8kd5BlRYNpL6ohEhl4CPk8EdR0qXmtBonsh4Z2kwXXAcPtiysZ6XA4kO1JLrgMN3cxthwEO2Z3UxI5O0L5W3DJvCco_4PCbRgUZWxlrR48NxmZ_bWkrhQb07txygvOOhB2T6lpX2CnkDPlS914jYP9QT8BBREEkERbr1zehVSt4NCCXNSC_Tnj9uXOj5GeYnm2Sw5OUXKEJspmDKEOoX4m_FKwlg3ywQfbWKDemVQYuHgmaPalDLLnAC8iydE50NLol07pQvhkK8zOjgz_zif7vENFH4152P9-ltFEvJVmwyqoN23Xuo7Aw")
|
||||||
|
|
||||||
|
statusCode, resBody, errs := agent.Bytes()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
fmt.Printf("request failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != fiber.StatusOK {
|
||||||
|
errorMessage := fmt.Sprintf("received an incorrect response from AddLeadsFields: %s", string(resBody))
|
||||||
|
fmt.Println(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(string(resBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GetUserInfo(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
cfgLogger := zap.NewDevelopmentConfig()
|
||||||
|
cfgLogger.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||||
|
cfgLogger.EncoderConfig.ConsoleSeparator = " "
|
||||||
|
|
||||||
|
logger, err := cfgLogger.Build()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rateLimiter := limiter.NewRateLimiter(ctx, 6, 1500*time.Millisecond)
|
||||||
|
|
||||||
|
amoclient := NewAmoClient(AmoDeps{
|
||||||
|
Logger: logger,
|
||||||
|
RedirectionURL: "https://squiz.pena.digital/squiz/amocrm/oauth",
|
||||||
|
IntegrationID: "2dbd6329-9be6-41f2-aa5f-964b9e723e49",
|
||||||
|
IntegrationSecret: "tNK3LwL4ovP0OBK4jKDHJ3646PqRJDOKQYgY6P2t6DCuV8LEzDzszTDY0Fhwmzc8",
|
||||||
|
RateLimiter: rateLimiter,
|
||||||
|
})
|
||||||
|
|
||||||
|
resp, err := amoclient.GetUserInfo("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjQxNjM4YTA3YjJkMzI5ZmJlMGJjNTlkMjlhZDFiYmJkZmQ5NDdlYTczYjc2YzkwYjliNDRiN2Q4YzdiOTZjMWE3Mjk1ZWJhNmQ2MjM3MzJjIn0.eyJhdWQiOiIyZGJkNjMyOS05YmU2LTQxZjItYWE1Zi05NjRiOWU3MjNlNDkiLCJqdGkiOiI0MTYzOGEwN2IyZDMyOWZiZTBiYzU5ZDI5YWQxYmJiZGZkOTQ3ZWE3M2I3NmM5MGI5YjQ0YjdkOGM3Yjk2YzFhNzI5NWViYTZkNjIzNzMyYyIsImlhdCI6MTcxNzgxOTIwMiwibmJmIjoxNzE3ODE5MjAyLCJleHAiOjE3MTc5MDU2MDIsInN1YiI6Ijg0MTM5NjkiLCJncmFudF90eXBlIjoiIiwiYWNjb3VudF9pZCI6MzAyMjg5OTcsImJhc2VfZG9tYWluIjoiYW1vY3JtLnJ1IiwidmVyc2lvbiI6Miwic2NvcGVzIjpbInB1c2hfbm90aWZpY2F0aW9ucyIsImZpbGVzIiwiY3JtIiwibm90aWZpY2F0aW9ucyJdLCJoYXNoX3V1aWQiOiI2NTAwNzFmNi04YzhhLTRlMzgtYWQyOC04YmEzMTgyN2Q2ZmQifQ.aXKe3crb56NIj9RBCrzb40jHxfE6e04kMnCZNnYmhjo2WPeStYBeb4meuIDFpskS6bGG_LRmExVmKOR4OnqWvV0wBXpOUrH0nvD_-NPkAiZbNh3viJFCqBJVfao5BEwC8SfcV2u235kgWcTcvk-nvrPUDuCcldF0bOrszACI3d5nDXxoiwgu9jTpuRq88CvxGHvAEECDrRWSsD6WTPMJmR6iIevn79zkC9nh_JC3Ph4_-waRSL3CbVNXFCXoAnD8Py8A1LCFlt9pRW6SXOopIOe25VgklTUkbDG1sPlFZneXqKpcJ72qPvLE7AKWaWkCSydvtAcNTnBLP7sgLUA4tA", "penadigitaltech.amocrm.ru")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
fmt.Println(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DealGet(t *testing.T) {
|
||||||
|
url := "https://penadigitaltech.amocrm.ru/api/v4/leads/44690375"
|
||||||
|
|
||||||
|
client := fiber.AcquireClient()
|
||||||
|
|
||||||
|
agent := client.Get(url)
|
||||||
|
agent.Set("Authorization", "Bearer "+"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjkxNjQxYzdiMGEzYTRjZGUzZmUxYmNiZDZiZmNhMDYxNDcyOTczMGIwZDMxNWIyYWFlYWJhYTRmNDAyMzlhYjZhYWNlZjQzMGQwNGIxMmY1In0.eyJhdWQiOiIyZGJkNjMyOS05YmU2LTQxZjItYWE1Zi05NjRiOWU3MjNlNDkiLCJqdGkiOiI5MTY0MWM3YjBhM2E0Y2RlM2ZlMWJjYmQ2YmZjYTA2MTQ3Mjk3MzBiMGQzMTViMmFhZWFiYWE0ZjQwMjM5YWI2YWFjZWY0MzBkMDRiMTJmNSIsImlhdCI6MTcxODY4MzIwMCwibmJmIjoxNzE4NjgzMjAwLCJleHAiOjE3MTg3Njk2MDAsInN1YiI6Ijg0MTM5NjkiLCJncmFudF90eXBlIjoiIiwiYWNjb3VudF9pZCI6MzAyMjg5OTcsImJhc2VfZG9tYWluIjoiYW1vY3JtLnJ1IiwidmVyc2lvbiI6Miwic2NvcGVzIjpbInB1c2hfbm90aWZpY2F0aW9ucyIsImZpbGVzIiwiY3JtIiwibm90aWZpY2F0aW9ucyJdLCJoYXNoX3V1aWQiOiJhMzBjOWQ1MS1hYTM4LTRlMDYtYWNlMy1iYTQ5MmE3NjE1ZmUifQ.mclcK1MHEIYG9nDSO6NdXIyvIKzd-2h7OrmE-7JjWpIj4WO9W6jUsIEwuJs8glbRT1wvf-SBV9p1Di1QSZE2-9k6exi7W6xgzoK1xLeukdcFd3yTEpDXfamBaMIvlAOyJQ8ZjyqE3Y3f983jUiabe_gGAEk8JxgQzkVNtmvjgeaf8qbHyAwPZ98DddrL91airQooEaT3kmqmXo9R1X0TCfMOh_23xqGH7TDrlQ0AuDo-VjUh5Merc_z6atAocSc1HwHpnjHgMj9Ib3KIenDqbeiUk4evtsOpDO2VdLrKQTPicQUDQGxShaiexF0oGVnoclFh2Cymqon_FuQDnnuyvg")
|
||||||
|
|
||||||
|
statusCode, resBody, errs := agent.Bytes()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
fmt.Println(errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != fiber.StatusOK {
|
||||||
|
fmt.Println(statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(string(resBody))
|
||||||
|
}
|
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
|
||||||
|
}
|
66
pkg/pena-social-auth/client.go
Normal file
66
pkg/pena-social-auth/client.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package pena_social_auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Deps struct {
|
||||||
|
PenaSocialAuthURL string
|
||||||
|
FiberClient *fiber.Client
|
||||||
|
Logger *zap.Logger
|
||||||
|
ReturnURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
penaSocialAuthURL string
|
||||||
|
fiberClient *fiber.Client
|
||||||
|
logger *zap.Logger
|
||||||
|
returnURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(deps Deps) *Client {
|
||||||
|
if deps.FiberClient == nil {
|
||||||
|
deps.FiberClient = fiber.AcquireClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
penaSocialAuthURL: deps.PenaSocialAuthURL,
|
||||||
|
fiberClient: deps.FiberClient,
|
||||||
|
returnURL: deps.ReturnURL,
|
||||||
|
logger: deps.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenAuthURLResp struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GenerateAmocrmAuthURL(accountID string) (string, error) {
|
||||||
|
url := c.penaSocialAuthURL + "?accessToken=" + accountID + "&returnUrl=" + c.returnURL
|
||||||
|
statusCode, resp, errs := c.fiberClient.Get(url).Bytes()
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for _, err := range errs {
|
||||||
|
c.logger.Error("error sending request in GenerateAmocrmAuthURL", zap.Error(err))
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("request GenerateAmocrmAuthURL failed: %v", errs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if statusCode != http.StatusOK {
|
||||||
|
errorMessage := fmt.Sprintf("received an incorrect response from GenerateAmocrmAuthURL: %d", statusCode)
|
||||||
|
c.logger.Error(errorMessage, zap.Int("status", statusCode))
|
||||||
|
return "", fmt.Errorf(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response GenAuthURLResp
|
||||||
|
err := json.Unmarshal(resp, &response)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("error unmarshal GenAuthURLResp:", zap.Error(err))
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.URL, nil
|
||||||
|
}
|
161
template/.gitignore.tmpl
Normal file
161
template/.gitignore.tmpl
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
# 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
|
29
template/cmd/{{.ProjectName}}/main.go.tmpl
Normal file
29
template/cmd/{{.ProjectName}}/main.go.tmpl
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"{{.Vars.ProjectName}}/internal/app"
|
||||||
|
|
||||||
|
"{{.Modules.logger.Import}}"
|
||||||
|
"{{.Modules.logger.ImportCore}}"
|
||||||
|
|
||||||
|
"{{.Modules.env.Import}}"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
{{.Modules.logger.Declaration "logger"}}
|
||||||
|
|
||||||
|
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var config app.Config
|
||||||
|
|
||||||
|
{{.Modules.env.Declaration "config"}}
|
||||||
|
|
||||||
|
if err := app.Run(ctx, config, logger); err != nil {
|
||||||
|
{{.Modules.logger.Message "Fatal" "Failed to run app" "Error" "err"}}
|
||||||
|
}
|
||||||
|
}
|
67
template/internal/app/app.go.tmpl
Normal file
67
template/internal/app/app.go.tmpl
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"{{.Modules.logger.Import}}"
|
||||||
|
)
|
||||||
|
|
||||||
|
{{.Modules.env.Struct}}
|
||||||
|
|
||||||
|
func Run(ctx context.Context, config Config, logger {{.Modules.logger.Type}}) error {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
logger.Error("Recovered from a panic", zap.Any("error", r))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
{{.Modules.logger.Message "info" "App started" "config" "config"}}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Инициализация репозиториев
|
||||||
|
{{range $key, $value := .LayersData.Repositories}}
|
||||||
|
{{$key}}Repository := {{$value.PackageName}}.New{{$value.Name}}Repository()
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
// Инициализация сервисов
|
||||||
|
{{range $key, $value := .LayersData.Services}}
|
||||||
|
{{$key}}Service := {{$value.PackageName}}.New{{$value.Name}}Service({{$key}}Repository)
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
// Инициализация контроллеров
|
||||||
|
{{range $key, $value := .LayersData.Controllers}}
|
||||||
|
{{$key}}Controller := {{$value.PackageName}}.New{{$value.Name}}Controller({{$key}}Service)
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
// Создание сервера
|
||||||
|
server := {{.LayersData.ServerData}}.NewServer({{.LayersData.ServerData}}.ServerConfig{
|
||||||
|
Controllers: []{{.LayersData.ServerData}}.Controller{
|
||||||
|
{{range $key, $value := .LayersData.Controllers}}
|
||||||
|
{{$key}}Controller,
|
||||||
|
{{end}}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := server.Start("Host + : + Port")
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Server startup error", zap.Error(err))
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Вывод маршрутов
|
||||||
|
server.ListRoutes()
|
||||||
|
|
||||||
|
<-ctx.Done()
|
||||||
|
|
||||||
|
logger.Info("App shutting down gracefully")
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
// Остановка сервера
|
||||||
|
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
79
tests/json/json_test.go
Normal file
79
tests/json/json_test.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/models"
|
||||||
|
"amocrm/internal/tools"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnmarshalJSON(t *testing.T) {
|
||||||
|
jsonData := []byte(`[
|
||||||
|
{
|
||||||
|
"field_id": 1,
|
||||||
|
"values": [
|
||||||
|
{"value": "Value1"},
|
||||||
|
{"value": {"file_uuid": "123e4567file_uuid", "version_uuid": "123e4567version_uuid", "file_name": "file.pdf", "file_size": 1}}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field_id": 2,
|
||||||
|
"values": [
|
||||||
|
{"value": "Value2"},
|
||||||
|
{"value": {"file_uuid": "98765432file_uuid", "version_uuid": "98765432version_uuid", "file_name": "file.wc", "file_size": 2}}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field_id": 3,
|
||||||
|
"values": [
|
||||||
|
{"value": "Value3"},
|
||||||
|
{"value": {"file_uuid": "abcdeffile_uuid", "version_uuid": "abcdefversion_uuid", "file_name": "file.txt", "file_size": 3}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
var fv []models.FieldsValues
|
||||||
|
err := json.Unmarshal(jsonData, &fv)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("UnmarshalJSON failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range fv {
|
||||||
|
fmt.Println(f)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 3, len(fv))
|
||||||
|
|
||||||
|
jsonAgain, err := json.Marshal(fv)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
fmt.Println(string(jsonAgain))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ContactRule(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
repo, err := dal.NewAmoDal(ctx, "host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
accountID := "64f2cd7a7047f28fdabf6d9e"
|
||||||
|
|
||||||
|
quiz, err := repo.QuizRepo.GetQuizById(ctx, accountID, 26166)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var quizConfig model.QuizContact
|
||||||
|
err = json.Unmarshal([]byte(quiz.Config), &quizConfig)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
currentFields, err := repo.AmoRepo.GetUserFieldsByID(ctx, 30228997)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
contactFieldsToCreate, forAdding := tools.ForContactRules(quizConfig, currentFields)
|
||||||
|
|
||||||
|
fmt.Println("contactFieldsToCreate", contactFieldsToCreate)
|
||||||
|
fmt.Println("forAdding", forAdding)
|
||||||
|
}
|
29
tests/limiter/limiter_test.go
Normal file
29
tests/limiter/limiter_test.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package limiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/workers/limiter"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Limiter(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
rateLimiter := limiter.NewRateLimiter(ctx, 6, 1500*time.Millisecond)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
count := 1
|
||||||
|
for {
|
||||||
|
if rateLimiter.Check() {
|
||||||
|
fmt.Println("GO", count)
|
||||||
|
count++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Println("SLEEP")
|
||||||
|
time.Sleep(1500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
time.Sleep(20 * time.Second)
|
||||||
|
}
|
757
tests/repository/repository_test.go
Normal file
757
tests/repository/repository_test.go
Normal file
@ -0,0 +1,757 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Repository(t *testing.T) {
|
||||||
|
//ctx := context.Background()
|
||||||
|
//repo, err := dal.NewAmoDal(ctx, "host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable")
|
||||||
|
//
|
||||||
|
//if err != nil {
|
||||||
|
// fmt.Println("err init postgres")
|
||||||
|
// panic(err)
|
||||||
|
//}
|
||||||
|
|
||||||
|
//err = webhookCreate(ctx, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = webhookUpdate(ctx, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//
|
||||||
|
//count := 0
|
||||||
|
//for {
|
||||||
|
// if count == 8 {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// time.Sleep(time.Second)
|
||||||
|
// tokens, err := checkExpired(ctx, repo)
|
||||||
|
// assert.NoError(t, err)
|
||||||
|
// if len(tokens) > 0 {
|
||||||
|
// fmt.Println(tokens)
|
||||||
|
// count++
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
//err = getAllTokens(ctx, t, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = createUser(ctx, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = updateUser(ctx, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = checkUsers(ctx, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = gettingUserFromCash(ctx, t, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = checkPipelines(ctx, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = gettingPipelinesFromCash(ctx, t, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = checkSteps(ctx, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = gettingStepsFromCash(ctx, t, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = checkTags(ctx, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = gettingTagsFromCash(ctx, t, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = checkFields(ctx, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = gettingFieldsFromCash(ctx, t, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = saveUtms(ctx, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = gettingUserUtm(ctx, t, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//err = deleteUserUTM(ctx, t, repo)
|
||||||
|
//assert.NoError(t, err)
|
||||||
|
//RuleTest(ctx, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
//func createUser(ctx context.Context, repo *dal.AmoDal) error {
|
||||||
|
// for i := 1; i < 10; i++ {
|
||||||
|
// accID := strconv.Itoa(i)
|
||||||
|
// err := repo.AmoRepo.CreateAccount(ctx, accID, model.User{
|
||||||
|
// AmoID: int32(i),
|
||||||
|
// Name: "Test" + strconv.Itoa(i),
|
||||||
|
// Email: "Test" + strconv.Itoa(i) + "@mail.test",
|
||||||
|
// Amouserid: int32(i),
|
||||||
|
// Subdomain: "Test",
|
||||||
|
// Country: "RUSSIA",
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func updateUser(ctx context.Context, repo *dal.AmoDal) error {
|
||||||
|
// var testUserInfo models.AmocrmUserInformation
|
||||||
|
// err := json.Unmarshal([]byte(jsonUserInfo), &testUserInfo)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// for i := 1; i < 10; i++ {
|
||||||
|
// accID := strconv.Itoa(i)
|
||||||
|
// info := model.User{
|
||||||
|
// Name: faker.String(),
|
||||||
|
// Subdomain: "pena",
|
||||||
|
// AmoID: 666,
|
||||||
|
// Amouserid: int32(i),
|
||||||
|
// Email: faker.Email(),
|
||||||
|
// Group: int32(i),
|
||||||
|
// Country: "Russia",
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if i%2 == 0 {
|
||||||
|
// info.Role = int32(i)
|
||||||
|
// info.ID = faker.Int64()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// err = repo.AmoRepo.CreateAccount(ctx, accID, info)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func checkUsers(ctx context.Context, repo *dal.AmoDal) error {
|
||||||
|
// var testUserInfo models.AmocrmUserInformation
|
||||||
|
// err := json.Unmarshal([]byte(jsonUserInfo), &testUserInfo)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// for i := 1; i < 10; i++ {
|
||||||
|
// if i%2 != 0 {
|
||||||
|
// info := model.User{
|
||||||
|
// Name: faker.String(),
|
||||||
|
// Email: faker.Email(),
|
||||||
|
// Group: int32(i),
|
||||||
|
// }
|
||||||
|
// info.Role = int32(i)
|
||||||
|
//
|
||||||
|
// err = repo.AmoRepo.CheckAndUpdateUsers(ctx, info)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return err
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func gettingUserFromCash(ctx context.Context, t *testing.T, repo *dal.AmoDal) error {
|
||||||
|
// for i := 1; i < 10; i++ {
|
||||||
|
// req := model.PaginationReq{
|
||||||
|
// Page: 1,
|
||||||
|
// Size: int32(i),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// resp, err := repo.AmoRepo.GettingUserWithPagination(ctx, &req, "5")
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fmt.Println(len(resp.Items))
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func checkPipelines(ctx context.Context, repo *dal.AmoDal) error {
|
||||||
|
// var testPipeline1 models.PipelineResponse
|
||||||
|
// err := json.Unmarshal([]byte(jsonPipelines), &testPipeline1)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testPipeline2 models.PipelineResponse
|
||||||
|
// err = json.Unmarshal([]byte(jsonPipelines2), &testPipeline2)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for i := 0; i < 9; i++ {
|
||||||
|
// err = repo.AmoRepo.CheckPipelines(ctx, tools.ToPipeline(testPipeline1.Embedded.Pipelines))
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for i := 0; i < 9; i++ {
|
||||||
|
// err = repo.AmoRepo.CheckPipelines(ctx, tools.ToPipeline(testPipeline2.Embedded.Pipelines))
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func gettingPipelinesFromCash(ctx context.Context, t *testing.T, repo *dal.AmoDal) error {
|
||||||
|
// for i := 1; i < 28; i++ {
|
||||||
|
// req := model.PaginationReq{
|
||||||
|
// Page: 1,
|
||||||
|
// Size: int32(i),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// resp, err := repo.AmoRepo.GetPipelinesWithPagination(ctx, &req, "4")
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fmt.Println(resp.Items)
|
||||||
|
// fmt.Println(len(resp.Items))
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func checkSteps(ctx context.Context, repo *dal.AmoDal) error {
|
||||||
|
// var testStep1 models.ResponseGetListSteps
|
||||||
|
// err := json.Unmarshal([]byte(jsonStep1), &testStep1)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testStep2 models.ResponseGetListSteps
|
||||||
|
// err = json.Unmarshal([]byte(jsonStep2), &testStep2)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for i := 0; i < 9; i++ {
|
||||||
|
// err = repo.AmoRepo.CheckSteps(ctx, tools.ToStep(testStep1.Embedded.Statuses))
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for i := 0; i < 9; i++ {
|
||||||
|
// err = repo.AmoRepo.CheckSteps(ctx, tools.ToStep(testStep2.Embedded.Statuses))
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func gettingStepsFromCash(ctx context.Context, t *testing.T, repo *dal.AmoDal) error {
|
||||||
|
// for i := 1; i < 46; i++ {
|
||||||
|
// req := model.PaginationReq{
|
||||||
|
// Page: 1,
|
||||||
|
// Size: int32(i),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// resp, err := repo.AmoRepo.GetStepsWithPagination(ctx, &req, "3")
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fmt.Println(resp.Items)
|
||||||
|
// fmt.Println(len(resp.Items))
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func checkTags(ctx context.Context, repo *dal.AmoDal) error {
|
||||||
|
// var testLeadsTags models.ResponseGetListTags
|
||||||
|
// err := json.Unmarshal([]byte(jsonLeadsTags), &testLeadsTags)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testLeadsTags2 models.ResponseGetListTags
|
||||||
|
// err = json.Unmarshal([]byte(jsonLeadsTags2), &testLeadsTags2)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testContactsTags models.ResponseGetListTags
|
||||||
|
// err = json.Unmarshal([]byte(jsonContactsTags), &testContactsTags)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testContactsTags2 models.ResponseGetListTags
|
||||||
|
// err = json.Unmarshal([]byte(jsonContactsTags2), &testContactsTags2)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testCompaniesTags models.ResponseGetListTags
|
||||||
|
// err = json.Unmarshal([]byte(jsonCompaniesTags), &testCompaniesTags)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testCompaniesTags2 models.ResponseGetListTags
|
||||||
|
// err = json.Unmarshal([]byte(jsonCompaniesTags2), &testCompaniesTags2)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testCustomersTags models.ResponseGetListTags
|
||||||
|
// err = json.Unmarshal([]byte(jsonCustomersTags), &testCustomersTags)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testCustomersTags2 models.ResponseGetListTags
|
||||||
|
// err = json.Unmarshal([]byte(jsonCustomersTags2), &testCustomersTags2)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// for i := 0; i < 9; i++ {
|
||||||
|
// accID := strconv.Itoa(i)
|
||||||
|
// err = repo.AmoRepo.CheckTags(ctx, tools.ToTag(testLeadsTags.Embedded.Tags, model.LeadsType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
// err = repo.AmoRepo.CheckTags(ctx, tools.ToTag(testCompaniesTags.Embedded.Tags, model.CompaniesType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
// err = repo.AmoRepo.CheckTags(ctx, tools.ToTag(testCustomersTags.Embedded.Tags, model.CustomersType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
// err = repo.AmoRepo.CheckTags(ctx, tools.ToTag(testContactsTags.Embedded.Tags, model.ContactsType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for i := 0; i < 9; i++ {
|
||||||
|
// accID := strconv.Itoa(i)
|
||||||
|
// err = repo.AmoRepo.CheckTags(ctx, tools.ToTag(testLeadsTags2.Embedded.Tags, model.LeadsType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
// err = repo.AmoRepo.CheckTags(ctx, tools.ToTag(testCompaniesTags2.Embedded.Tags, model.CompaniesType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
// err = repo.AmoRepo.CheckTags(ctx, tools.ToTag(testCustomersTags2.Embedded.Tags, model.CustomersType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
// err = repo.AmoRepo.CheckTags(ctx, tools.ToTag(testContactsTags2.Embedded.Tags, model.ContactsType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func gettingTagsFromCash(ctx context.Context, t *testing.T, repo *dal.AmoDal) error {
|
||||||
|
// for i := 1; i < 73; i++ {
|
||||||
|
// req := model.PaginationReq{
|
||||||
|
// Page: 1,
|
||||||
|
// Size: int32(i),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// resp, err := repo.AmoRepo.GetTagsWithPagination(ctx, &req, "2")
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fmt.Println(resp.Items)
|
||||||
|
// fmt.Println(len(resp.Items))
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func checkFields(ctx context.Context, repo *dal.AmoDal) error {
|
||||||
|
// var testLeadsFields models.ResponseGetListFields
|
||||||
|
// err := json.Unmarshal([]byte(jsonLeadsFields), &testLeadsFields)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testLeadsFields2 models.ResponseGetListFields
|
||||||
|
// err = json.Unmarshal([]byte(jsonLeadsFields2), &testLeadsFields2)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testContactsFields models.ResponseGetListFields
|
||||||
|
// err = json.Unmarshal([]byte(jsonContactsFields), &testContactsFields)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testContactsFields2 models.ResponseGetListFields
|
||||||
|
// err = json.Unmarshal([]byte(jsonContactsFields2), &testContactsFields2)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testCompaniesFields models.ResponseGetListFields
|
||||||
|
// err = json.Unmarshal([]byte(jsonCompaniesFields), &testCompaniesFields)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testCompaniesFields2 models.ResponseGetListFields
|
||||||
|
// err = json.Unmarshal([]byte(jsonCompaniesFields2), &testCompaniesFields2)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testCustomersFields models.ResponseGetListFields
|
||||||
|
// err = json.Unmarshal([]byte(jsonCustomersFields), &testCustomersFields)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var testCustomersFields2 models.ResponseGetListFields
|
||||||
|
// err = json.Unmarshal([]byte(jsonCustomersFields2), &testCustomersFields2)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for i := 0; i < 9; i++ {
|
||||||
|
// accID := strconv.Itoa(i)
|
||||||
|
// err = repo.AmoRepo.CheckFields(ctx, tools.ToField(testLeadsFields.Embedded.CustomFields, model.LeadsType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// err = repo.AmoRepo.CheckFields(ctx, tools.ToField(testCompaniesFields.Embedded.CustomFields, model.CompaniesType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// err = repo.AmoRepo.CheckFields(ctx, tools.ToField(testCustomersFields.Embedded.CustomFields, model.CustomersType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// err = repo.AmoRepo.CheckFields(ctx, tools.ToField(testContactsFields.Embedded.CustomFields, model.ContactsType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for i := 0; i < 9; i++ {
|
||||||
|
// accID := strconv.Itoa(i)
|
||||||
|
// err = repo.AmoRepo.CheckFields(ctx, tools.ToField(testLeadsFields2.Embedded.CustomFields, model.LeadsType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// err = repo.AmoRepo.CheckFields(ctx, tools.ToField(testCompaniesFields2.Embedded.CustomFields, model.CompaniesType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// err = repo.AmoRepo.CheckFields(ctx, tools.ToField(testCustomersFields2.Embedded.CustomFields, model.CustomersType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// err = repo.AmoRepo.CheckFields(ctx, tools.ToField(testContactsFields2.Embedded.CustomFields, model.ContactsType), accID)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func gettingFieldsFromCash(ctx context.Context, t *testing.T, repo *dal.AmoDal) error {
|
||||||
|
// for i := 1; i < 73; i++ {
|
||||||
|
// req := model.PaginationReq{
|
||||||
|
// Page: 1,
|
||||||
|
// Size: int32(i),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// resp, err := repo.AmoRepo.GetFieldsWithPagination(ctx, &req, "1")
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fmt.Println(resp.Items)
|
||||||
|
// fmt.Println(len(resp.Items))
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func webhookCreate(ctx context.Context, repo *dal.AmoDal) error {
|
||||||
|
// for i := 1; i < 10; i++ {
|
||||||
|
// accID := strconv.Itoa(i)
|
||||||
|
// err := repo.AmoRepo.WebhookCreate(ctx, model.Token{
|
||||||
|
// RefreshToken: faker.UUID(),
|
||||||
|
// AccessToken: faker.UUID(),
|
||||||
|
// AccountID: accID,
|
||||||
|
// AuthCode: faker.String(),
|
||||||
|
// Expiration: time.Now().Unix() + 10,
|
||||||
|
// CreatedAt: time.Now().Unix(),
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func webhookUpdate(ctx context.Context, repo *dal.AmoDal) error {
|
||||||
|
// var tokens []model.Token
|
||||||
|
// for i := 1; i < 10; i++ {
|
||||||
|
// accID := strconv.Itoa(i)
|
||||||
|
// tokens = append(tokens, model.Token{
|
||||||
|
// RefreshToken: faker.UUID(),
|
||||||
|
// AccessToken: faker.UUID(),
|
||||||
|
// AccountID: accID,
|
||||||
|
// Expiration: time.Now().Unix() + int64(i),
|
||||||
|
// CreatedAt: time.Now().Unix(),
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// err := repo.AmoRepo.WebhookUpdate(ctx, tokens)
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func checkExpired(ctx context.Context, repo *dal.AmoDal) ([]model.Token, error) {
|
||||||
|
// tokens, err := repo.AmoRepo.CheckExpired(ctx)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return tokens, nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func getAllTokens(ctx context.Context, t *testing.T, repo *dal.AmoDal) error {
|
||||||
|
// tokens, err := repo.AmoRepo.GetAllTokens(ctx)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// assert.Equal(t, len(tokens), 9)
|
||||||
|
//
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func saveUtms(ctx context.Context, repo *dal.AmoDal) error {
|
||||||
|
// for i := 1; i < 10; i++ {
|
||||||
|
// var utms []model.UTM
|
||||||
|
// for j := 1; j < 50; j++ {
|
||||||
|
// utm := model.UTM{
|
||||||
|
// Name: strconv.Itoa(i),
|
||||||
|
// Quizid: int32(j),
|
||||||
|
// Accountid: faker.Int32(),
|
||||||
|
// Amofieldid: int32(j),
|
||||||
|
// Createdat: time.Now().Unix(),
|
||||||
|
// }
|
||||||
|
// utms = append(utms, utm)
|
||||||
|
// }
|
||||||
|
// resp, err := repo.AmoRepo.SavingUserUtm(ctx, utms, strconv.Itoa(i))
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// fmt.Println(resp)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func gettingUserUtm(ctx context.Context, t *testing.T, repo *dal.AmoDal) error {
|
||||||
|
// for i := 1; i < 50; i++ {
|
||||||
|
// req := model.PaginationReq{
|
||||||
|
// Page: 1,
|
||||||
|
// Size: int32(i),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// resp, err := repo.AmoRepo.GettingUserUtm(ctx, &req, "1", 1)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fmt.Println(resp.Items)
|
||||||
|
// fmt.Println(len(resp.Items))
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func deleteUserUTM(ctx context.Context, t *testing.T, repo *dal.AmoDal) error {
|
||||||
|
// req := model.PaginationReq{
|
||||||
|
// Page: 1,
|
||||||
|
// Size: 50,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// resp, err := repo.AmoRepo.GettingUserUtm(ctx, &req, "5", 5)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var fordetete []int32
|
||||||
|
// for _, r := range resp.Items {
|
||||||
|
// fordetete = append(fordetete, int32(r.ID))
|
||||||
|
// }
|
||||||
|
// err = repo.AmoRepo.DeletingUserUtm(ctx, &model.ListDeleteUTMIDsReq{
|
||||||
|
// fordetete,
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for i := 1; i < 50; i++ {
|
||||||
|
// req := model.PaginationReq{
|
||||||
|
// Page: 1,
|
||||||
|
// Size: int32(i),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// resp, err := repo.AmoRepo.GettingUserUtm(ctx, &req, "5", 5)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fmt.Println(resp.Items)
|
||||||
|
// fmt.Println(len(resp.Items))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func RuleTest(ctx context.Context, repo *dal.AmoDal) {
|
||||||
|
// testMap := make(map[int]int)
|
||||||
|
// testMap[11111] = 11111
|
||||||
|
// err := repo.AmoRepo.SetQuizSettings(ctx, &model.RulesReq{
|
||||||
|
// PerformerID: 2,
|
||||||
|
// PipelineID: 1,
|
||||||
|
// StepID: 1,
|
||||||
|
// Utms: make([]int32, 3),
|
||||||
|
// Fieldsrule: model.Fieldsrule{
|
||||||
|
// Lead: []model.FieldRule{
|
||||||
|
// {
|
||||||
|
// Questionid: testMap,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// Contact: []model.FieldRule{
|
||||||
|
// {
|
||||||
|
// Questionid: testMap,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// Company: []model.FieldRule{
|
||||||
|
// {
|
||||||
|
// Questionid: testMap,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// Customer: []model.FieldRule{
|
||||||
|
// {
|
||||||
|
// Questionid: testMap,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }, "3", 3)
|
||||||
|
//
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// testMap[222] = 3
|
||||||
|
//
|
||||||
|
// err = repo.AmoRepo.ChangeQuizSettings(ctx, &model.RulesReq{
|
||||||
|
// PerformerID: 111,
|
||||||
|
// PipelineID: 111,
|
||||||
|
// StepID: 111,
|
||||||
|
// Utms: make([]int32, 10),
|
||||||
|
// Fieldsrule: model.Fieldsrule{
|
||||||
|
// Lead: []model.FieldRule{
|
||||||
|
// {
|
||||||
|
// Questionid: testMap,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// Contact: []model.FieldRule{
|
||||||
|
// {
|
||||||
|
// Questionid: testMap,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// Company: []model.FieldRule{
|
||||||
|
// {
|
||||||
|
// Questionid: testMap,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// Customer: []model.FieldRule{
|
||||||
|
// {
|
||||||
|
// Questionid: testMap,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }, "3", 3)
|
||||||
|
//
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err, "UPDATE")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// rule3, err := repo.AmoRepo.GettingQuizRules(ctx, 1)
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
// fmt.Println(rule3)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func Test_UTM(t *testing.T) {
|
||||||
|
// ctx := context.Background()
|
||||||
|
// repo, err := dal.NewAmoDal(ctx, "host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable")
|
||||||
|
//
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println("err init postgres")
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var utms []model.UTM
|
||||||
|
//
|
||||||
|
// for i := 0; i < 10; i++ {
|
||||||
|
// utm := model.UTM{
|
||||||
|
// Quizid: int32(i) + 1,
|
||||||
|
// Accountid: 30228997,
|
||||||
|
// Name: faker.String(),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// utms = append(utms, utm)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// utmss, err := repo.AmoRepo.SavingUserUtm(ctx, utms, "64f2cd7a7047f28fdabf6d9e")
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var ids []int32
|
||||||
|
//
|
||||||
|
// for _, id := range utmss.Ids {
|
||||||
|
// ids = append(ids, int32(id))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// resp, err := repo.AmoRepo.GetUtmsByID(ctx, ids)
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fmt.Println(resp)
|
||||||
|
//}
|
||||||
|
|
||||||
|
func Test_GetCurrentAccount(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
repo, err := dal.NewAmoDal(ctx, "host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("err init postgres")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accountID := "64f2cd7a7047f28fdabf6d9e"
|
||||||
|
|
||||||
|
account, err := repo.AmoRepo.GetCurrentAccount(ctx, accountID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
fmt.Println(account)
|
||||||
|
}
|
1456
tests/repository/test_vars.go
Normal file
1456
tests/repository/test_vars.go
Normal file
File diff suppressed because it is too large
Load Diff
25
tests/tools/construct_test.go
Normal file
25
tests/tools/construct_test.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"amocrm/internal/tools"
|
||||||
|
"fmt"
|
||||||
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_ConstructAmoTags(t *testing.T) {
|
||||||
|
color := "Red"
|
||||||
|
currentTags := []model.Tag{
|
||||||
|
{ID: 1, Name: "First", Amoid: 111, Color: &color},
|
||||||
|
{ID: 2, Name: "Iron Man", Amoid: 222, Color: nil},
|
||||||
|
{ID: 3, Name: "Lionel Messi", Amoid: 333, Color: nil},
|
||||||
|
}
|
||||||
|
ruleTags := model.TagsToAdd{
|
||||||
|
Lead: []int64{111},
|
||||||
|
Contact: []int64{222, 3333},
|
||||||
|
Company: []int64{},
|
||||||
|
Customer: []int64{},
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(tools.ConstructAmoTags(currentTags, ruleTags))
|
||||||
|
}
|
BIN
tools/migrate
Executable file
BIN
tools/migrate
Executable file
Binary file not shown.
Loading…
Reference in New Issue
Block a user