From 8729a55b2222e7d2d230951bdc144711769394b3 Mon Sep 17 00:00:00 2001 From: Kirill Date: Thu, 29 Jun 2023 14:50:48 +0000 Subject: [PATCH] feat: set access token on get tariffs --- README.md | 4 + docs/diagram/README.md | 182 ++++++++++++++++++ internal/interface/client/hubadmin.go | 18 +- .../interface/controller/rest/cart/cart.go | 53 +++-- internal/models/auth.go | 1 + internal/models/cart.go | 4 +- internal/service/cart/cart.go | 23 ++- internal/utils/authenticator.go | 5 +- 8 files changed, 240 insertions(+), 50 deletions(-) create mode 100644 docs/diagram/README.md diff --git a/README.md b/README.md index 45ddc35..a59416a 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,7 @@ JWT_PUBLIC_KEY - публичный ключ для верификации jwt JWT_ISSUER - издатель токена JWT_AUDIENCE - аудитория, которая может верифицировать токен ``` + +## Полезные ссылки: + +- [**Диаграммы**](./docs/diagram/README.md) diff --git a/docs/diagram/README.md b/docs/diagram/README.md new file mode 100644 index 0000000..df1ba03 --- /dev/null +++ b/docs/diagram/README.md @@ -0,0 +1,182 @@ +# Диаграммы последовательности и зависимостей + +## UseCase оплата корзины (успешно) + +```plantuml +participant Frontend +participant CustomerService +participant HubAdminService +participant DiscountService +database CustomerServiceDB + +Frontend -> CustomerService : Запрос на оплату +CustomerService -> CustomerServiceDB : Поиск аккаунта по ID пользователья единой авторизации +CustomerService <-- CustomerServiceDB : Найденный аккаунт +CustomerService -> HubAdminService : Получение тарифов из массива id тарифов в корзине +CustomerService <-- HubAdminService : Список тарифов +CustomerService -> CustomerService : Подсчитывается сумма тарифов +CustomerService <-- CustomerService : Сумма тарифов +CustomerService -> DiscountService : Приминение скидок +CustomerService <-- DiscountService : Сформированная цена после применения скидок +CustomerService -> CustomerService : Проверка на наличие средств +CustomerService <-- CustomerService : Средств достаточно +CustomerService -> CustomerServiceDB : Списание средств с кошелька +CustomerService <-- CustomerServiceDB : Обновлённый аккаунт +CustomerService -> CustomerServiceDB : Запись в историю успех оплаты (ошибка игнорируется) +CustomerService <-- CustomerServiceDB : Записанная история +CustomerService -> CustomerServiceDB : Очистка корзины +CustomerService <-- CustomerServiceDB : Обновлённый аккаунт +Frontend <-- CustomerService : Ответ об успешной оплате корзины +``` + +--- + +## UseCase оплата корзины (недостаточно средств) + +```plantuml +participant Frontend +participant CustomerService +participant HubAdminService +participant DiscountService +database CustomerServiceDB + +Frontend -> CustomerService : Запрос на оплату +CustomerService -> CustomerServiceDB : Поиск аккаунта по ID пользователья единой авторизации +CustomerService <-- CustomerServiceDB : Найденный аккаунт +CustomerService -> HubAdminService : Получение тарифов из массива id тарифов в корзине +CustomerService <-- HubAdminService : Список тарифов +CustomerService -> CustomerService : Подсчитывается сумма тарифов +CustomerService <-- CustomerService : Сумма тарифов +CustomerService -> DiscountService : Приминение скидок +CustomerService <-- DiscountService : Сформированная цена после применения скидок +CustomerService -> CustomerService : Проверка на наличие средств +CustomerService <-- CustomerService : Средств не достаточно +Frontend <-- CustomerService : Ответ об ошибке по причине нехватки средств (insufficient funds: 50) +``` + +--- + +## UseCase получение ссылки на оплату для пополнения средств в корзине (успешно) + +```plantuml +participant Frontend +participant CustomerService +participant PaymentService +database CustomerServiceDB + +Frontend -> CustomerService : Запрос на получение ссылки +CustomerService -> CustomerService : Определение способа оплаты +CustomerService <-- CustomerService : Успешно определено +CustomerService -> PaymentService : Запрос на получение платёжной ссылки +CustomerService <-- PaymentService : Платёжная ссылка +Frontend <-- CustomerService : Платёжная ссылка +``` + +--- + +## UseCase изменения валюты в кошельке (успешно) + +```plantuml +participant Frontend +participant CustomerService +participant CbrfService +database CustomerServiceDB + +Frontend -> CustomerService : Запрос на изменения валюты +CustomerService -> CustomerServiceDB : Получение аккаунта по ID пользователья единой авторизации +CustomerService <-- CustomerServiceDB : Найденный аккаунт +CustomerService -> CbrfService : Перевод валюты с одной на другую +CustomerService <-- CbrfService : Результат перевода валюты +CustomerService -> CustomerServiceDB : Обновление кошелька аккаунта +CustomerService <-- CustomerServiceDB : Обновлённый аккаунт +Frontend <-- CustomerService : Обновлённый аккаунт +``` + +--- + +## UseCase регистрация аккаунта (успешно) + +```plantuml +participant Frontend +participant CustomerService +participant AuthService +database CustomerServiceDB + +Frontend -> CustomerService : Запрос на регистрацию аккаунта +CustomerService -> CustomerServiceDB : Поиск аккаунта по ID пользователя единой авторизации +CustomerService <-- CustomerServiceDB : Ошибка: аккаунт не найден +CustomerService -> AuthService : Поиск пользователя единой авторизации по ID +CustomerService <-- AuthService : Найденный пользователь +CustomerService -> CustomerServiceDB : Создание аккаунта с прикрученным \nID пользователя единой авторизации +CustomerService <-- CustomerServiceDB : Созданный аккаунт +Frontend <-- CustomerService : Созданный аккаунт +``` + +--- + +## UseCase уведомление об успешной оплате (успешно, id платежей не равны) + +```plantuml +participant PaymentService +participant CustomerService +database CustomerServiceDB + +PaymentService -> CustomerService : Запрос отправки уведомления об успешной оплате +CustomerService -> CustomerServiceDB : Поиск аккаунта по ID пользователя единой авторизации +CustomerService <-- CustomerServiceDB : Найденный аккаунт +CustomerService -> CustomerService : Сравнение ID платежа с прошлым +CustomerService <-- CustomerService : ID платежей не равны +CustomerService -> CustomerServiceDB : Изменение кошелька аккаунта +CustomerService <-- CustomerServiceDB : Обновлённый аккаунт +CustomerService -> CustomerServiceDB : Создание истории об успешном пополнении средств +CustomerService <-- CustomerServiceDB : Созданная история +PaymentService <-- CustomerService : Успешный ответ +``` + +--- + +## UseCase уведомление об успешной оплате (успешно, id платежей равны) + +```plantuml +participant PaymentService +participant CustomerService +database CustomerServiceDB + +PaymentService -> CustomerService : Запрос отправки уведомления об успешной оплате +CustomerService -> CustomerServiceDB : Поиск аккаунта по ID пользователя единой авторизации +CustomerService <-- CustomerServiceDB : Найденный аккаунт +CustomerService -> CustomerService : Сравнение ID платежа с прошлым +CustomerService <-- CustomerService : ID платежей равны +PaymentService <-- CustomerService : Успешный ответ +``` + +## Диаграмма зависимостей + +```plantuml +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +Container(CustomerService, "Customer Service", "Golang", "Сервис customer") +Container(AuthService, "Auth Service", "NodeJS", "Микросервис единой авторизации") +Container(DiscountService, "Discount Service", "Golang", "Микросервис скидок") +Container(CbrfService, "CBRF Worker Service", "Golang", "Микросервис по выдаче и перевода курсов валют") +Container(PaymentService, "Payment Service", "Golang", "Платёжный микросервис") +Container(HubadminService, "Hub admin Service", "NodeJS", "Севрис управления тарифами и привелегиями") + +ContainerDb(CustomerDB, "Customer Service Database", "MongoDB", "Хранит информацию об аккаунтах") +ContainerDb(AuthServiceDB, "Auth Service Database", "MongoDB", "Хранит информацию о пользователях и сессиях") +ContainerDb(CbrfServiceDB, "CBRF Worker Database", "MongoDB", "Хранит информацию о курсах валют") +ContainerDb(PaymentServiceDB, "Payment Service Database", "MongoDB", "Хранит информацию о платежах и их состоянии") +ContainerDb(HubadminServiceDB, "Hubadmin service Database", "MongoDB", "Хранит информацию о тарифах и привелегиях") + +Rel(CustomerService, AuthService, "Использует для получения актуальной информации о пользователе") +Rel(CustomerService, CbrfService, "Использует для перевода валюты с одного курса на другой") +Rel(CustomerService, PaymentService, "Использует для проведения оплаты, получения платёжной ссылки и уведомления об успешной/не_успешной оплате") +Rel(CustomerService, HubadminService, "Использует для получения информации о тарифах") +Rel(CustomerService, DiscountService, "Использует для приминения скидок") + +Rel_R(CustomerService, CustomerDB, "Читает/Записывает") +Rel_R(AuthService, AuthServiceDB, "Читает/Записывает") +Rel_R(CbrfService, CbrfServiceDB, "Читает/Записывает") +Rel_R(PaymentService, PaymentServiceDB, "Читает/Записывает") +Rel_R(HubadminService, HubadminServiceDB, "Читает/Записывает") +``` \ No newline at end of file diff --git a/internal/interface/client/hubadmin.go b/internal/interface/client/hubadmin.go index e4e21eb..1da6d31 100644 --- a/internal/interface/client/hubadmin.go +++ b/internal/interface/client/hubadmin.go @@ -38,18 +38,18 @@ func NewHubadminClient(deps HubadminClientDeps) *HubadminClient { } } -func (receiver *HubadminClient) GetTariff(ctx context.Context, tariffID string) (*models.Tariff, errors.Error) { +func (receiver *HubadminClient) GetTariff(ctx context.Context, accessToken string, tariffID string) (*models.Tariff, errors.Error) { tariffURL, err := url.JoinPath(receiver.urls.Tariff, tariffID) if err != nil { - return nil, errors.New( - fmt.Errorf("failed to join path on of : %w", err), - errors.ErrInternalError, - ) + return nil, errors.New(fmt.Errorf("failed to join path on of : %w", err), errors.ErrInternalError) } response, err := client.Get[models.Tariff, models.FastifyError](ctx, &client.RequestSettings{ - URL: tariffURL, - Headers: map[string]string{"Content-Type": "application/json"}, + URL: tariffURL, + Headers: map[string]string{ + "Content-Type": "application/json", + "Authorization": fmt.Sprintf("Bearer %s", accessToken), + }, }) if err != nil { receiver.logger.Error("failed to request get tariff on of ", @@ -77,11 +77,11 @@ func (receiver *HubadminClient) GetTariff(ctx context.Context, tariffID string) return response.Body, nil } -func (receiver *HubadminClient) GetTariffs(ctx context.Context, tarriffIDs []string) ([]models.Tariff, errors.Error) { +func (receiver *HubadminClient) GetTariffs(ctx context.Context, accessToken string, tarriffIDs []string) ([]models.Tariff, errors.Error) { tariffs := make([]models.Tariff, len(tarriffIDs)) for index, tariffID := range tarriffIDs { - tariff, err := receiver.GetTariff(ctx, tariffID) + tariff, err := receiver.GetTariff(ctx, accessToken, tariffID) if err != nil { receiver.logger.Error("failed to get tariff on of ", zap.Error(err), zap.String("tariffID", tariffID)) return []models.Tariff{}, err diff --git a/internal/interface/controller/rest/cart/cart.go b/internal/interface/controller/rest/cart/cart.go index 23e1d33..5544ee1 100644 --- a/internal/interface/controller/rest/cart/cart.go +++ b/internal/interface/controller/rest/cart/cart.go @@ -16,8 +16,8 @@ import ( type cartService interface { Remove(ctx context.Context, userID, itemID string) ([]string, errors.Error) - Add(ctx context.Context, userID, itemID string) ([]string, errors.Error) - Pay(ctx context.Context, userID string) (link string, err errors.Error) + Add(context.Context, *models.AddItemToCart) ([]string, errors.Error) + Pay(ctx context.Context, token, userID string) (link string, err errors.Error) } type Deps struct { @@ -49,11 +49,7 @@ func (receiver *Controller) Remove(ctx echo.Context, params swagger.RemoveFromCa userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string) if !ok { receiver.logger.Error("failed to convert jwt payload to string on of ") - - return errors.HTTP(ctx, errors.New( - fmt.Errorf("failed to convert jwt payload to string: %s", userID), - errors.ErrInvalidArgs, - )) + return errors.HTTP(ctx, errors.NewWithMessage("failed to convert jwt payload to string", errors.ErrInvalidArgs)) } if validate.IsStringEmpty(params.Id) { @@ -65,11 +61,7 @@ func (receiver *Controller) Remove(ctx echo.Context, params swagger.RemoveFromCa cartItems, err := receiver.cartService.Remove(ctx.Request().Context(), userID, params.Id) if err != nil { - receiver.logger.Error( - "failed to remove item from cart on of ", - zap.Error(err), - ) - + receiver.logger.Error("failed to remove item from cart on of ", zap.Error(err)) return errors.HTTP(ctx, err) } @@ -80,11 +72,13 @@ func (receiver *Controller) Add(ctx echo.Context, params swagger.Add2cartParams) userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string) if !ok { receiver.logger.Error("failed to convert jwt payload to string on of ") + return errors.HTTP(ctx, errors.NewWithMessage("failed to convert jwt payload to string", errors.ErrInvalidArgs)) + } - return errors.HTTP(ctx, errors.New( - fmt.Errorf("failed to convert jwt payload to string: %s", userID), - errors.ErrInvalidArgs, - )) + token, ok := ctx.Get(models.AuthJWTDecodedAccessTokenKey).(string) + if !ok { + receiver.logger.Error("failed to convert access token payload to string on of ") + return errors.HTTP(ctx, errors.NewWithMessage("failed to convert access token payload to string", errors.ErrInvalidArgs)) } if validate.IsStringEmpty(params.Id) { @@ -94,13 +88,13 @@ func (receiver *Controller) Add(ctx echo.Context, params swagger.Add2cartParams) )) } - cartItems, err := receiver.cartService.Add(ctx.Request().Context(), userID, params.Id) + cartItems, err := receiver.cartService.Add(ctx.Request().Context(), &models.AddItemToCart{ + UserID: userID, + TariffID: params.Id, + AccessToken: token, + }) if err != nil { - receiver.logger.Error( - "failed to add item to cart on of ", - zap.Error(err), - ) - + receiver.logger.Error("failed to add item to cart on of ", zap.Error(err)) return errors.HTTP(ctx, err) } @@ -111,17 +105,18 @@ func (receiver *Controller) Pay(ctx echo.Context) error { userID, ok := ctx.Get(models.AuthJWTDecodedUserIDKey).(string) if !ok { receiver.logger.Error("failed to convert jwt payload to string on of ") - - return errors.HTTP(ctx, errors.New( - fmt.Errorf("failed to convert jwt payload to string: %s", userID), - errors.ErrInvalidArgs, - )) + return errors.HTTP(ctx, errors.NewWithMessage("failed to convert jwt payload to string", errors.ErrInvalidArgs)) } - link, err := receiver.cartService.Pay(ctx.Request().Context(), userID) + token, ok := ctx.Get(models.AuthJWTDecodedAccessTokenKey).(string) + if !ok { + receiver.logger.Error("failed to convert access token payload to string on of ") + return errors.HTTP(ctx, errors.NewWithMessage("failed to convert access token payload to string", errors.ErrInvalidArgs)) + } + + link, err := receiver.cartService.Pay(ctx.Request().Context(), token, userID) if err != nil { receiver.logger.Error("failed to pay cart on of ", zap.Error(err)) - return errors.HTTP(ctx, err) } diff --git a/internal/models/auth.go b/internal/models/auth.go index 9322913..c34f42f 100644 --- a/internal/models/auth.go +++ b/internal/models/auth.go @@ -14,3 +14,4 @@ type User struct { } const AuthJWTDecodedUserIDKey = "userID" +const AuthJWTDecodedAccessTokenKey = "access-token" diff --git a/internal/models/cart.go b/internal/models/cart.go index 1dcec30..24e5978 100644 --- a/internal/models/cart.go +++ b/internal/models/cart.go @@ -1,5 +1,7 @@ package models type AddItemToCart struct { - ID string `json:"id"` + UserID string + TariffID string + AccessToken string } diff --git a/internal/service/cart/cart.go b/internal/service/cart/cart.go index 4540844..0988c23 100644 --- a/internal/service/cart/cart.go +++ b/internal/service/cart/cart.go @@ -23,8 +23,8 @@ type accountRepository interface { } type hubadminClient interface { - GetTariff(ctx context.Context, tariffID string) (*models.Tariff, errors.Error) - GetTariffs(ctx context.Context, tarriffIDs []string) ([]models.Tariff, errors.Error) + GetTariff(ctx context.Context, accessToken, tariffID string) (*models.Tariff, errors.Error) + GetTariffs(ctx context.Context, accessToken string, tarriffIDs []string) ([]models.Tariff, errors.Error) } type discountClient interface { @@ -103,21 +103,26 @@ func (receiver *Service) Remove(ctx context.Context, userID, itemID string) ([]s return account.Cart, nil } -func (receiver *Service) Add(ctx context.Context, userID, itemID string) ([]string, errors.Error) { - tariff, err := receiver.hubadminClient.GetTariff(ctx, itemID) +func (receiver *Service) Add(ctx context.Context, request *models.AddItemToCart) ([]string, errors.Error) { + tariff, err := receiver.hubadminClient.GetTariff(ctx, request.AccessToken, request.TariffID) if err != nil { - receiver.logger.Error("failed to get tariff on of ", zap.Error(err), zap.String("tariffID", itemID)) + receiver.logger.Error("failed to get tariff on of ", + zap.Error(err), + zap.String("tariffID", request.TariffID), + zap.String("accessToken", request.AccessToken), + ) + return []string{}, err } if tariff == nil { return []string{}, errors.New( - fmt.Errorf("failed to get tariff <%s> on of : tariff not found", itemID), + fmt.Errorf("failed to get tariff <%s> on of : tariff not found", request.TariffID), errors.ErrNotFound, ) } - account, err := receiver.repository.AddItemToCart(ctx, userID, itemID) + account, err := receiver.repository.AddItemToCart(ctx, request.UserID, request.TariffID) if err != nil { receiver.logger.Error("failed to add item to cart on of ", zap.Error(err)) return []string{}, err @@ -126,14 +131,14 @@ func (receiver *Service) Add(ctx context.Context, userID, itemID string) ([]stri return account.Cart, nil } -func (receiver *Service) Pay(ctx context.Context, userID string) (string, errors.Error) { +func (receiver *Service) Pay(ctx context.Context, accessToken string, userID string) (string, errors.Error) { account, err := receiver.repository.FindByUserID(ctx, userID) if err != nil { receiver.logger.Error("failed to find account on of ", zap.String("userID", userID), zap.Error(err)) return "", err } - tariffs, err := receiver.hubadminClient.GetTariffs(ctx, account.Cart) + tariffs, err := receiver.hubadminClient.GetTariffs(ctx, accessToken, account.Cart) if err != nil { receiver.logger.Error("failed to get tarrifs on of ", zap.Strings("cart", account.Cart), zap.Error(err)) return "", err diff --git a/internal/utils/authenticator.go b/internal/utils/authenticator.go index 0680d61..ce10d9a 100644 --- a/internal/utils/authenticator.go +++ b/internal/utils/authenticator.go @@ -42,7 +42,7 @@ func authenticate(ctx context.Context, jwtUtil *JWT, input *openapi3filter.Authe } // if the JWS is valid, we have a JWT, which will contain a bunch of claims. - token, validateErr := jwtUtil.Validate(jws) + userID, validateErr := jwtUtil.Validate(jws) if validateErr != nil { return validateErr } @@ -51,7 +51,8 @@ func authenticate(ctx context.Context, jwtUtil *JWT, input *openapi3filter.Authe // access the claims data we generate in here. echoCtx := middleware.GetEchoContext(ctx) - echoCtx.Set(models.AuthJWTDecodedUserIDKey, token) + echoCtx.Set(models.AuthJWTDecodedUserIDKey, userID) + echoCtx.Set(models.AuthJWTDecodedAccessTokenKey, jws) return nil }