docxTemplater/handlers/amo.go
Danil Solovyov fba01b2518 Changes:
- Добавлена проверка токенов при запросе /amo/state
  - Исправлена работа с amo oauth-токенами
  - Добавлены примеры шаблонизатора
  - Добавлена возможность генерации и создания шаблонов из примеров
  - В ответ генератора добавлены ссылки для скачивания сгенерированных файлов
  - Вернул очиску temp после загрузки файла в Яндекс.Диск
  - yadisk.UploadResources - добавлено ожидание окончания отправки файла и таймаут в 5 секунд для отправки файла
  - Добавлены эндпоинты для загрузки файлов в хранилища
  - Актуализирован генератор по вебхуку
  - Обновлены генератор по данным
  - Обновлен генератор по лиду
2022-11-25 00:37:47 +05:00

336 lines
8.5 KiB
Go

package handlers
import (
"errors"
"fmt"
"github.com/Pena-Co-Ltd/amocrm_templategen_back/dal/model"
"github.com/Pena-Co-Ltd/amocrm_templategen_back/tools"
"github.com/gorilla/schema"
"go.uber.org/zap"
"golang.org/x/oauth2"
"net/http"
"os"
"strconv"
"time"
)
type ReqAmoSaveToken struct {
AccessToken string `json:"access_token" schema:"access_token"`
Code string `json:"code" schema:"code"`
ClientID string `json:"client_id" schema:"client_id"`
ExpiresIn int64 `json:"expires_in" schema:"expires_in"`
TokenType string `json:"token_type" schema:"token_type"`
RefreshToken string `json:"refresh_token" schema:"refresh_token"`
State string `json:"state" schema:"state"`
FromWidget string `json:"from_widget" schema:"from_widget"`
Referer string `json:"referer" schema:"referer"`
Platform string `json:"platform" schema:"platform"` // Вообще без понятия, что это
}
func (h *Handlers) AmoSaveToken(w http.ResponseWriter, r *http.Request) {
var req ReqAmoSaveToken
err := r.ParseForm()
if err != nil {
h.reportError(w, err, http.StatusBadRequest)
return
}
err = schema.NewDecoder().Decode(&req, r.Form)
if err != nil {
h.reportError(w, err, http.StatusBadRequest)
return
}
if req.AccessToken == "" && req.Code == "" {
err = errors.New("token required")
h.reportError(w, err, http.StatusBadRequest)
return
}
if req.State == "" {
err = errors.New("state required")
h.reportError(w, err, http.StatusBadRequest)
return
}
var state tools.StateToken
err = tools.DecryptTokenRC4(req.State, &state)
if err != nil {
h.reportError(w, err, http.StatusUnauthorized)
return
}
token := &oauth2.Token{
AccessToken: req.AccessToken,
RefreshToken: req.RefreshToken,
TokenType: req.TokenType,
Expiry: time.Now().Add(time.Duration(req.ExpiresIn) * time.Second),
}
amoClient, err := h.Amo.NewClient(r.Context(), req.Referer, token, req.Code)
if err != nil {
h.reportError(w, err, http.StatusForbidden)
return
}
token = amoClient.Token
amoAcc, err := amoClient.GetAccount()
if err != nil {
h.reportError(w, err, http.StatusInternalServerError)
return
}
// Insert/Update token in DB
_, err = h.dal.Amo.InsertOrUpdate(r.Context(), &model.Amo{
UserID: state.UserID,
AccountID: strconv.FormatInt(amoAcc.Id, 10),
AccessToken: token.AccessToken,
RefreshToken: token.RefreshToken,
FromWidget: req.FromWidget,
Referer: req.Referer,
Subdomain: amoAcc.Subdomain,
ExpiresIn: token.Expiry,
TokenType: token.TokenType,
})
if err != nil {
h.reportError(w, err, http.StatusInternalServerError)
return
}
http.Redirect(w, r, state.RedirectUrl, http.StatusPermanentRedirect)
}
type RespAmoState struct {
GenCount int `json:"gen_count"`
AuthYandexUrl string `json:"auth_yandex_url"`
AuthGoogleUrl string `json:"auth_google_url"`
AuthAmoUrl string `json:"auth_amo_url"`
Storages map[string]interface{} `json:"storages"`
Examples any `json:"examples"`
InvalidTokens map[string]any `json:"invalid_tokens"` // Невалидные токены, которые не получилось обновить
Visibility []int64 `json:"visibility"`
Creation []int64 `json:"creation"`
Delete []int64 `json:"delete"`
}
// AmoState - получить актуальное состояние аккаунта пользователя в Amo
func (h *Handlers) AmoState(w http.ResponseWriter, r *http.Request) {
// TODO: get pena jwt
amoData := getAmoByJwt(r)
if amoData == nil {
h.reportError(w, ErrorUnauthorized, http.StatusUnauthorized)
return
}
yaStorages, err := h.dal.YaDisk.GetListByUserID(r.Context(), amoData.UserID)
if err != nil {
h.reportError(w, err, http.StatusInternalServerError)
return
}
gStorages, err := h.dal.GDisk.GetListByUserID(r.Context(), amoData.UserID)
if err != nil {
h.reportError(w, err, http.StatusInternalServerError)
return
}
storages := map[string]interface{}{
"yadisk": yaStorages,
"gdisk": gStorages,
}
redirectUri := "https://" + amoData.Referer + h.opts.AmoRedirectUrn
authYandexUrl, err := h.YaDisk.GenerateOAuthUrl(amoData.UserID, redirectUri)
if err != nil {
h.reportError(w, err, http.StatusInternalServerError)
return
}
authGoogleUrl, err := h.GDisk.GenerateOAuthUrl(amoData.UserID, redirectUri)
if err != nil {
h.reportError(w, err, http.StatusInternalServerError)
return
}
authAmoUrl, err := h.Amo.GenerateOAuthUrl(amoData.UserID, redirectUri)
if err != nil {
h.reportError(w, err, http.StatusInternalServerError)
return
}
//Add examples
var examples any
pathExamples := "./static/examples/docx"
fsExamples, err := os.ReadDir(pathExamples)
if err != nil {
examples = fmt.Sprintf("err = %v", err)
} else {
result := map[string]string{}
pathExamples = "static/examples/docx"
for _, file := range fsExamples {
downloadUrl := fmt.Sprintf("https://%v/%v/%v", h.opts.Domain, pathExamples, file.Name())
result[file.Name()] = downloadUrl
}
examples = result
}
// Check tokens
invalidTokens := map[string]any{}
// Yandex tokens
for _, storage := range yaStorages {
if storage.Token().Valid() {
continue
}
token, err := h.YaDisk.RefreshToken(r.Context(), storage.Token())
if err == nil {
_, err = h.dal.YaDisk.InsertOrUpdate(r.Context(), &model.YaDisk{
ID: storage.ID,
AccessToken: token.AccessToken,
RefreshToken: token.RefreshToken,
ExpiresIn: token.Expiry,
TokenType: token.TokenType,
})
}
if err != nil {
h.logger.Error("ErrorCheckYandexTokens", zap.Error(err))
invalidTokens["yadisk"] = storage
}
}
// Google tokens
for _, storage := range gStorages {
if storage.Token().Valid() {
continue
}
token, err := h.GDisk.RefreshToken(r.Context(), storage.Token())
if err == nil {
_, err = h.dal.GDisk.InsertOrUpdate(r.Context(), &model.GDisk{
ID: storage.ID,
RefreshToken: token.RefreshToken,
ExpiresIn: token.Expiry,
TokenType: token.TokenType,
})
}
if err != nil {
h.logger.Error("ErrorCheckGoogleTokens", zap.Error(err))
invalidTokens["gdisk"] = storage
}
}
// Amo tokens
amoList, err := h.dal.Amo.GetListByUserID(r.Context(), amoData.UserID)
if err != nil {
h.reportError(w, err, http.StatusInternalServerError)
return
}
for _, amo := range amoList {
if amo.Token().Valid() {
continue
}
token, err := h.Amo.RefreshToken(r.Context(), amo.Token(), amo.Referer)
if err == nil {
_, err = h.dal.Amo.InsertOrUpdate(r.Context(), &model.Amo{
ID: amo.ID,
AccountID: amo.AccountID,
AccessToken: token.AccessToken,
RefreshToken: token.RefreshToken,
ExpiresIn: token.Expiry,
TokenType: token.TokenType,
})
}
if err != nil {
h.logger.Error("ErrorCheckAmoTokens", zap.Error(err))
invalidTokens["amo"] = amo
}
}
resp := RespAmoState{
GenCount: 97,
AuthYandexUrl: authYandexUrl,
AuthGoogleUrl: authGoogleUrl,
AuthAmoUrl: authAmoUrl,
Storages: storages,
Visibility: amoData.AccessRules.Visibility,
Creation: amoData.AccessRules.Creation,
Delete: amoData.AccessRules.Delete,
Examples: examples,
InvalidTokens: invalidTokens,
}
sendResponse(w, 200, resp)
}
type ReqAmoAccessRules struct {
Visibility []int64 `json:"visibility"`
Creation []int64 `json:"creation"`
Delete []int64 `json:"delete"`
}
// AmoAccessRules - задать правила для пользователя
func (h *Handlers) AmoAccessRules(w http.ResponseWriter, r *http.Request) {
var req ReqAmoAccessRules
amoData := getAmoByJwt(r)
if amoData == nil {
h.reportError(w, ErrorUnauthorized, http.StatusUnauthorized)
return
}
isAdmin := r.Context().Value("amoIsAdmin").(bool)
if !isAdmin {
h.reportError(w, errors.New("need admin access"), http.StatusUnauthorized)
return
}
err := decodePost(&req, r)
if err != nil {
h.reportError(w, err, http.StatusBadRequest)
return
}
if (req.Visibility == nil || len(req.Visibility) == 0) &&
(req.Creation == nil || len(req.Creation) == 0) &&
(req.Delete == nil || len(req.Delete) == 0) {
h.reportError(w, errors.New("empty request"), http.StatusBadRequest)
return
}
err = h.dal.Amo.UpdateAccessRules(r.Context(), amoData.ID, &model.AmoAccessRules{
Visibility: req.Visibility,
Creation: req.Creation,
Delete: req.Delete,
})
if err != nil {
h.reportError(w, err, http.StatusInternalServerError)
return
}
w.WriteHeader(200)
}