some progress

This commit is contained in:
Pavel 2024-09-20 17:41:33 +03:00
parent 0ccee75e14
commit 345228a19b
12 changed files with 386 additions and 95 deletions

@ -1,16 +1,18 @@
package app
import (
"amocrm/internal/controllers"
"amocrm/internal/service"
"amocrm/internal/tools"
"context"
"errors"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/brokers"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/controllers"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/initialize"
http "penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/server"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/service"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/workers/limiter"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/pkg/bitrixClient"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/pkg/closer"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/utils"
"time"
"go.uber.org/zap"
@ -30,12 +32,6 @@ func Run(ctx context.Context, config initialize.Config, logger *zap.Logger) erro
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))
@ -47,46 +43,39 @@ func Run(ctx context.Context, config initialize.Config, logger *zap.Logger) erro
Logger: logger,
})
encrypt := utils.NewEncrypt(config.PublicKey, config.PrivateKey)
bitrixRepo, err := dal.NewBitrixDal(ctx, config.PostgresCredentials)
if err != nil {
logger.Error("error init bitrix 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,
//})
// todo search in bitrix https://apidocs.bitrix24.ru/limits.html
rateLimiter := limiter.NewRateLimiter(ctx, 6, 1500*time.Millisecond)
//redisRepo := repository.NewRepository(repository.Deps{
// RedisClient: redisClient,
// Logger: logger,
//})
bitrixClientApi := bitrixClient.NewBitrixClient(bitrixClient.BitrixDeps{
Logger: logger,
RedirectionURL: config.ReturnURL,
IntegrationID: config.IntegrationID,
IntegrationSecret: config.IntegrationSecret,
RateLimiter: rateLimiter,
})
svc := service.NewService(service.Deps{
Repository: amoRepo,
Logger: logger,
SocialAuthClient: socialAithClient,
AmoClient: amoClient,
Producer: producer,
Repository: bitrixRepo,
Logger: logger,
BitrixClient: bitrixClientApi,
Producer: producer,
Config: config,
Encrypt: encrypt,
})
cntrlDeps := controllers.Deps{
Service: svc,
Logger: logger,
Verify: tools.NewVerify(config.IntegrationSecret, config.IntegrationID),
RedirectURL: config.RedirectURL,
Encrypt: encrypt,
}
controller := controllers.NewController(cntrlDeps)

@ -4,26 +4,25 @@ import (
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/service"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/utils"
)
type Deps struct {
Service *service.Service
Logger *zap.Logger
Verify *tools.Verify
Encrypt *utils.Encrypt
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,
}
}
@ -53,7 +52,7 @@ func (c *Controller) Name() string {
type WebhookController struct {
service *service.Service
logger *zap.Logger
verify *tools.Verify
encrypt *utils.Encrypt
redirectURL string
}
@ -61,14 +60,14 @@ func NewWebhookController(deps Deps) *WebhookController {
return &WebhookController{
service: deps.Service,
logger: deps.Logger,
verify: deps.Verify,
encrypt: deps.Encrypt,
redirectURL: deps.RedirectURL,
}
}
func (c *WebhookController) Register(router fiber.Router) {
router.Get("/create", c.WebhookCreate)
router.Delete("/delete", c.WebhookDelete)
//router.Delete("/delete", c.WebhookDelete)
}
func (c *WebhookController) Name() string {

@ -1,50 +1,53 @@
package controllers
import (
"amocrm/internal/service"
"amocrm/internal/tools"
"fmt"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/service"
)
// todo под битриск переписать надо
// контроллер на который редиректятся ответы по авторизации битрикса
// https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=99&LESSON_ID=2486
func (c *WebhookController) WebhookCreate(ctx *fiber.Ctx) error {
code := ctx.Query("code") // первый авторизационный код
domain := ctx.Query("domain") // домен портала, на котором происходит авторизация
state := ctx.Query("state") // строка которая передавалась в соц аус сервисе
scope := ctx.Query("scope") // разделенный запятыми список прав доступа к REST API, которые портал предоставляет приложению
member_id := ctx.Query("member_id") // уникальный идентификатор портала, на котором происходит авторизация
server_domain := ctx.Query("server_domain") // домен сервера авторизации
code := ctx.Query("code") // первый авторизационный код
domain := ctx.Query("domain") // домен портала, на котором происходит авторизация
state := ctx.Query("state") // значение, переданное в первом запросе
scope := ctx.Query("scope") // список прав доступа к REST API
memberID := ctx.Query("member_id") // уникальный идентификатор портала
serverDomain := ctx.Query("server_domain") // домен сервера авторизации
if code == "" || domain == "" || memberID == "" || serverDomain == "" {
c.logger.Error("Missing required fields", zap.String("code", code), zap.String("domain", domain), zap.String("member_id", memberID), zap.String("server_domain", serverDomain))
return ctx.Status(fiber.StatusBadRequest).SendString("Missing required fields")
}
if state == "" {
return ctx.Status(fiber.StatusBadRequest).SendString("State don't be empty")
return ctx.Status(fiber.StatusBadRequest).SendString("State cannot be empty")
}
accountID, _, err := tools.DeserializeProtobufMessage(state)
accountID, err := c.encrypt.DecryptStr([]byte(state))
if err != nil {
c.logger.Error("error Deserialize Protobuf Message", zap.Error(err))
return ctx.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
c.logger.Error("Error deserializing Protobuf message", zap.Error(err))
return ctx.Status(fiber.StatusInternalServerError).SendString("Failed to process state parameter")
}
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")
if accountID == "" {
c.logger.Error("AccountID is missing from state")
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid state parameter")
}
req := service.ParamsWebhookCreate{
Code: code,
Referer: referer,
AccountID: accountID,
FromWidget: fromWidget,
Platform: platform,
Code: code,
Domain: domain,
AccountID: accountID,
MemberID: memberID,
Scope: scope,
ServerDomain: serverDomain,
}
err = c.service.WebhookCreate(ctx.Context(), req)
if err != nil {
c.logger.Error("error create webhook", zap.Error(err))
c.logger.Error("Error creating webhook", zap.Error(err))
return ctx.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("Internal Server Error: %v", err.Error()))
}

@ -6,6 +6,7 @@ import (
"log"
)
// todo config need upd with bitrix data
type Config struct {
AppName string `env:"APP_NAME" envDefault:"amocrm"`
HTTPHost string `env:"HTTP_HOST" envDefault:"localhost"`
@ -17,6 +18,15 @@ type Config struct {
RedisAddr string `env:"REDIS_ADDR" envDefault:"localhost:6379"`
RedisPassword string `env:"REDIS_PASS" envDefault:"admin"`
RedisDB int `env:"REDIS_DB" envDefault:"2"`
IntegrationID string `env:"INTEGRATION_ID" envDefault:"2dbd6329-9be6-41f2-aa5f-964b9e723e49"` // код интеграции
RedirectURL string `env:"REDIRECT_URL" envDefault:"https://squiz.pena.digital/integrations"`
// публичный и приватные ключи для енкрипта и декрипта стейта который передаем в битрикс а потом он приходит
PublicKey string
PrivateKey string
// секрет интеграции
IntegrationSecret string `env:"INTEGRATION_SECRET" envDefault:"tNK3LwL4ovP0OBK4jKDHJ3646PqRJDOKQYgY6P2t6DCuV8LEzDzszTDY0Fhwmzc8"`
// урл на который будет возвращен пользователь после авторизации это webhook/create get
ReturnURL string `env:"RETURN_URL" envDefault:"https://squiz.pena.digital/squiz/amocrm/oauth"`
}
func LoadConfig() (*Config, error) {

@ -0,0 +1,64 @@
package models
type CreateWebHookReq struct {
ClientID string `json:"client_id"` // id интеграции
ClientSecret string `json:"client_secret"` // Секрет интеграции
GrantType string `json:"grant_type"` // Тип авторизационных данных (для кода авторизации authorization_code)
Code string `json:"code"` // Полученный код авторизации
}
type CreateWebHookResp struct {
AccessToken string `json:"access_token"` // access token в формате JWT
ClientEndpoint string `json:"client_endpoint"`
Domain string `json:"domain"`
ExpiresIn int64 `json:"expires_in"` // время жизни токена в секундах
MemberID string `json:"member_id"` // ид пользователя
RefreshToken string `json:"refresh_token"` // токен для обновления access Token
Scope string `json:"scope"`
}
type UpdateWebHookReq struct {
ClientID string `json:"client_id"` // id интеграции
ClientSecret string `json:"client_secret"` // Секрет интеграции
GrantType string `json:"grant_type"` // Тип авторизационных данных (для кода авторизации authorization_code) refresh_token tut
RefreshToken string `json:"refresh_token"` // Refresh токен
}
type WebHookRequest interface {
SetClientID(str string)
SetClientSecret(str string)
GetGrantType() string
GetToken() string
}
func (req *CreateWebHookReq) SetClientID(str string) {
req.ClientID = str
}
func (req *CreateWebHookReq) SetClientSecret(str string) {
req.ClientSecret = str
}
func (req *CreateWebHookReq) GetGrantType() string {
return req.GrantType
}
func (req *CreateWebHookReq) GetToken() string {
return req.Code
}
func (req *UpdateWebHookReq) SetClientID(str string) {
req.ClientID = str
}
func (req *UpdateWebHookReq) SetClientSecret(str string) {
req.ClientSecret = str
}
func (req *UpdateWebHookReq) GetGrantType() string {
return req.GrantType
}
func (req *UpdateWebHookReq) GetToken() string {
return req.RefreshToken
}

@ -0,0 +1,18 @@
package models
type FieldsType string
const (
FieldTypeLeads FieldsType = "leads"
FieldTypeCompany FieldsType = "company"
FieldTypeContact FieldsType = "contact"
)
type Company struct {
}
type Lead struct {
}
type Contact struct {
}

@ -0,0 +1,20 @@
package models
type Category struct {
ID int64 `json:"id"`
Name string `json:"name"`
Sort int64 `json:"sort"`
EntityTypeId int64 `json:"entityTypeId"`
IsDefault BitrixIsDefault `json:"isDefault"`
}
type CategoryResponse struct {
Categories []Category `json:"categories"`
}
type BitrixIsDefault string
const (
BitrixIsDefaultY BitrixIsDefault = "Y"
BitrixIsDefaultN BitrixIsDefault = "N"
)

@ -3,29 +3,37 @@ package service
import (
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/brokers"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/initialize"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/pkg/bitrixClient"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/utils"
)
type Deps struct {
Repository *dal.BitrixDal
Logger *zap.Logger
AmoClient *bitrixClient.Bitrix
Producer *brokers.Producer
Repository *dal.BitrixDal
Logger *zap.Logger
BitrixClient *bitrixClient.Bitrix
Producer *brokers.Producer
Config initialize.Config
Encrypt *utils.Encrypt
}
type Service struct {
repository *dal.BitrixDal
logger *zap.Logger
amoClient *bitrixClient.Bitrix
producer *brokers.Producer
repository *dal.BitrixDal
logger *zap.Logger
bitrixClient *bitrixClient.Bitrix
producer *brokers.Producer
config initialize.Config
encrypt *utils.Encrypt
}
func NewService(deps Deps) *Service {
return &Service{
repository: deps.Repository,
logger: deps.Logger,
amoClient: deps.AmoClient,
producer: deps.Producer,
repository: deps.Repository,
logger: deps.Logger,
bitrixClient: deps.BitrixClient,
producer: deps.Producer,
config: deps.Config,
encrypt: deps.Encrypt,
}
}

@ -4,6 +4,8 @@ import (
"context"
"database/sql"
"go.uber.org/zap"
"net/url"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
)
@ -55,14 +57,24 @@ func (s *Service) GetCurrentAccount(ctx context.Context, accountID string) (*mod
}
func (s *Service) ConnectAccount(ctx context.Context, accountID string) (*model.ConnectAccountResp, error) {
link, err := s.socialAuthClient.GenerateAmocrmAuthURL(accountID)
state, err := s.encrypt.EncryptStr(accountID)
if err != nil {
s.logger.Error("error sending request to pena social auth service:", zap.Error(err))
s.logger.Error("error encrypting account state", zap.Error(err))
return nil, err
}
oauthURL := url.URL{
Scheme: "https",
Host: "portal.bitrix24.com",
Path: "/oauth/authorize/",
RawQuery: url.Values{
"client_id": {s.config.IntegrationID},
"state": {string(state)},
}.Encode(),
}
response := model.ConnectAccountResp{
Link: link,
Link: oauthURL.String(),
}
return &response, nil

@ -4,15 +4,17 @@ import (
"context"
"errors"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors"
)
type ParamsWebhookCreate struct {
Code string // Authorization 20 минут
Referer string // адрес аккаунта пользователя
AccountID string // строка которая передавалась в соц аус сервисе
FromWidget string
Platform string // ru/global 1/2
Code string
Domain string
AccountID string
MemberID string
Scope string
ServerDomain string
}
func (s *Service) WebhookCreate(ctx context.Context, req ParamsWebhookCreate) error {
@ -26,7 +28,7 @@ func (s *Service) WebhookCreate(ctx context.Context, req ParamsWebhookCreate) er
message := models.KafkaMessage{
AccountID: req.AccountID,
AuthCode: req.Code,
RefererURL: req.Referer,
RefererURL: req.Domain,
Type: models.UserCreate,
}
@ -41,7 +43,7 @@ func (s *Service) WebhookCreate(ctx context.Context, req ParamsWebhookCreate) er
message := models.KafkaMessage{
AccountID: req.AccountID,
AuthCode: req.Code,
RefererURL: req.Referer,
RefererURL: req.Domain,
Type: models.UserReLogin,
}
@ -54,11 +56,11 @@ func (s *Service) WebhookCreate(ctx context.Context, req ParamsWebhookCreate) er
return nil
}
func (s *Service) WebhookDelete(ctx context.Context, bitrixID int) error {
err := s.repository.BitrixRepo.WebhookDelete(ctx, bitrixID)
if err != nil {
s.logger.Error("error canceled bitrix integration", zap.Error(err))
return err
}
return nil
}
//func (s *Service) WebhookDelete(ctx context.Context, bitrixID int) error {
// err := s.repository.BitrixRepo.WebhookDelete(ctx, bitrixID)
// if err != nil {
// s.logger.Error("error canceled bitrix integration", zap.Error(err))
// return err
// }
// return nil
//}

@ -44,11 +44,12 @@ func NewBitrixClient(deps BitrixDeps) *Bitrix {
}
}
// https://dev.1c-bitrix.ru/rest_help/users/user_search.php
func (b *Bitrix) GetUserList(accesToken string, domain string) (*models.ResponseGetListUsers, error) {
for {
if b.rateLimiter.Check() {
uri := fmt.Sprintf("https://%s/rest/user.search", domain)
agent := b.fiberClient.Get(uri)
agent := b.fiberClient.Post(uri)
agent.Set("Authorization", "Bearer "+accesToken)
statusCode, resBody, errs := agent.Bytes()
if len(errs) > 0 {
@ -76,3 +77,138 @@ func (b *Bitrix) GetUserList(accesToken string, domain string) (*models.Response
time.Sleep(b.rateLimiter.Interval)
}
}
// https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=99&LESSON_ID=2486
func (b *Bitrix) CreateWebHook(req models.WebHookRequest, domain string) (*models.CreateWebHookResp, error) {
for {
if b.rateLimiter.Check() {
req.SetClientID(b.integrationID)
req.SetClientSecret(b.integrationSecret)
bodyBytes, err := json.Marshal(req)
if err != nil {
b.logger.Error("error marshal req in CreateWebHook:", zap.Error(err))
return nil, err
}
agent := b.fiberClient.Get(fmt.Sprintf("https://%s/oauth/token/", domain))
agent.Set("Content-Type", "application/json").Body(bodyBytes)
statusCode, resBody, errs := agent.Bytes()
if len(errs) > 0 {
for _, err = range errs {
b.logger.Error("error sending request in CreateWebHook for create or update tokens", zap.Error(err))
}
return nil, fmt.Errorf("request failed: %v", errs[0])
}
if statusCode != fiber.StatusOK {
errorMessage := fmt.Sprintf("received an incorrect response from CreateWebHook: %s", string(resBody))
b.logger.Error(errorMessage, zap.Int("status", statusCode))
return nil, fmt.Errorf(errorMessage)
}
var tokens models.CreateWebHookResp
err = json.Unmarshal(resBody, &tokens)
if err != nil {
b.logger.Error("error unmarshal CreateWebHookResp:", zap.Error(err))
return nil, err
}
return &tokens, nil
}
time.Sleep(b.rateLimiter.Interval)
}
}
// todo воронки и шаги надо понятиь что и как вообще обращаться ничо не понятно
// todo fieeeeelds
func (b *Bitrix) GetListFields(fieldType models.FieldsType, accessToken string, domain string) (interface{}, error) {
for {
if b.rateLimiter.Check() {
switch fieldType {
case models.FieldTypeCompany:
fullURL := fmt.Sprintf("https://%s/rest/crm.deal.userfield.list", domain)
agent := b.fiberClient.Post(fullURL)
agent.Set("Authorization", "Bearer "+accessToken)
statusCode, resBody, errs := agent.Bytes()
if len(errs) > 0 {
for _, err := range errs {
b.logger.Error("error sending request in GetListFields", zap.Error(err))
}
return nil, fmt.Errorf("request GetListFields failed: %v", errs[0])
}
if statusCode != fiber.StatusOK {
errorMessage := fmt.Sprintf("received an incorrect response from GetListFields: %s", string(resBody))
b.logger.Error(errorMessage, zap.Int("status", statusCode))
return nil, fmt.Errorf(errorMessage)
}
var listFields models.Company
err := json.Unmarshal(resBody, &listFields)
if err != nil {
b.logger.Error("error unmarshal models.Company:", zap.Error(err))
return nil, err
}
return string(resBody), nil
case models.FieldTypeLeads:
fullURL := fmt.Sprintf("https://%s/rest/crm.lead.fields", domain)
agent := b.fiberClient.Post(fullURL)
agent.Set("Authorization", "Bearer "+accessToken)
statusCode, resBody, errs := agent.Bytes()
if len(errs) > 0 {
for _, err := range errs {
b.logger.Error("error sending request in GetListFields", zap.Error(err))
}
return nil, fmt.Errorf("request GetListFields failed: %v", errs[0])
}
if statusCode != fiber.StatusOK {
errorMessage := fmt.Sprintf("received an incorrect response from GetListFields: %s", string(resBody))
b.logger.Error(errorMessage, zap.Int("status", statusCode))
return nil, fmt.Errorf(errorMessage)
}
var listFields models.Lead
err := json.Unmarshal(resBody, &listFields)
if err != nil {
b.logger.Error("error unmarshal models.Lead:", zap.Error(err))
return nil, err
}
return listFields, nil
case models.FieldTypeContact:
fullURL := fmt.Sprintf("https://%s/rest/crm.contact.fields", domain)
agent := b.fiberClient.Post(fullURL)
agent.Set("Authorization", "Bearer "+accessToken)
statusCode, resBody, errs := agent.Bytes()
if len(errs) > 0 {
for _, err := range errs {
b.logger.Error("error sending request in GetListFields", zap.Error(err))
}
return nil, fmt.Errorf("request GetListFields failed: %v", errs[0])
}
if statusCode != fiber.StatusOK {
errorMessage := fmt.Sprintf("received an incorrect response from GetListFields: %s", string(resBody))
b.logger.Error(errorMessage, zap.Int("status", statusCode))
return nil, fmt.Errorf(errorMessage)
}
var listFields models.Contact
err := json.Unmarshal(resBody, &listFields)
if err != nil {
b.logger.Error("error unmarshal models.Contact:", zap.Error(err))
return nil, err
}
return listFields, nil
}
}
time.Sleep(b.rateLimiter.Interval)
}
}

@ -0,0 +1,30 @@
package bitrixClient
import (
"context"
"fmt"
"go.uber.org/zap"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/models"
"penahub.gitlab.yandexcloud.net/backend/quiz/bitrix/internal/workers/limiter"
"testing"
"time"
)
func TestGetListFields(t *testing.T) {
ctx := context.Background()
lim := limiter.NewRateLimiter(ctx, 50, 2*time.Second)
logger := zap.NewNop()
b := NewBitrixClient(BitrixDeps{
Logger: logger,
RedirectionURL: "test",
IntegrationID: "test",
IntegrationSecret: "test",
RateLimiter: lim,
})
result, err := b.GetListFields(models.FieldTypeCompany, "7d7bed660000071b00717f9200000001000007fa355b6d9628d7a8e452400c7234fb2b", "b24-ld76ub.bitrix24.ru")
if err != nil {
fmt.Println(err)
}
fmt.Println(result)
}