diff options
author | Arsentiy Milchakov <milcars@mapswithme.com> | 2017-06-20 13:04:38 +0300 |
---|---|---|
committer | Arsentiy Milchakov <milcars@mapswithme.com> | 2017-06-20 13:04:38 +0300 |
commit | 7471d5acb83e7dcf06783b3467607d2200de4bff (patch) | |
tree | 337c6bcc73cdcfe6158303f78800a7ad82ff48a3 /partners_api | |
parent | df51b0c0e83cbd312f14cdf727f15975464fb52e (diff) |
[partners_api] viator
Diffstat (limited to 'partners_api')
-rw-r--r-- | partners_api/CMakeLists.txt | 2 | ||||
-rw-r--r-- | partners_api/partners_api.pro | 2 | ||||
-rw-r--r-- | partners_api/partners_api_tests/CMakeLists.txt | 1 | ||||
-rw-r--r-- | partners_api/partners_api_tests/partners_api_tests.pro | 1 | ||||
-rw-r--r-- | partners_api/partners_api_tests/viator_tests.cpp | 77 | ||||
-rw-r--r-- | partners_api/viator_api.cpp | 245 | ||||
-rw-r--r-- | partners_api/viator_api.hpp | 46 |
7 files changed, 374 insertions, 0 deletions
diff --git a/partners_api/CMakeLists.txt b/partners_api/CMakeLists.txt index 91cbc42813..9d0ecd6a56 100644 --- a/partners_api/CMakeLists.txt +++ b/partners_api/CMakeLists.txt @@ -21,6 +21,8 @@ set( rb_ads.hpp uber_api.cpp uber_api.hpp + viator_api.cpp + viator_api.hpp ) add_library(${PROJECT_NAME} ${SRC}) diff --git a/partners_api/partners_api.pro b/partners_api/partners_api.pro index 61e2fe682a..178ffbf797 100644 --- a/partners_api/partners_api.pro +++ b/partners_api/partners_api.pro @@ -17,6 +17,7 @@ SOURCES += \ opentable_api.cpp \ rb_ads.cpp \ uber_api.cpp \ + viator_api.cpp \ HEADERS += \ ads_base.hpp \ @@ -28,3 +29,4 @@ HEADERS += \ opentable_api.hpp \ rb_ads.hpp \ uber_api.hpp \ + viator_api.hpp \ diff --git a/partners_api/partners_api_tests/CMakeLists.txt b/partners_api/partners_api_tests/CMakeLists.txt index 2978e32cd5..4fe9c140d4 100644 --- a/partners_api/partners_api_tests/CMakeLists.txt +++ b/partners_api/partners_api_tests/CMakeLists.txt @@ -10,6 +10,7 @@ set( mopub_tests.cpp rb_tests.cpp uber_tests.cpp + viator_tests.cpp ) omim_add_test(${PROJECT_NAME} ${SRC}) diff --git a/partners_api/partners_api_tests/partners_api_tests.pro b/partners_api/partners_api_tests/partners_api_tests.pro index 7b6e225e38..c819af6212 100644 --- a/partners_api/partners_api_tests/partners_api_tests.pro +++ b/partners_api/partners_api_tests/partners_api_tests.pro @@ -32,3 +32,4 @@ SOURCES += \ mopub_tests.cpp \ rb_tests.cpp \ uber_tests.cpp \ + viator_tests.cpp \ diff --git a/partners_api/partners_api_tests/viator_tests.cpp b/partners_api/partners_api_tests/viator_tests.cpp new file mode 100644 index 0000000000..6a0bf9eed4 --- /dev/null +++ b/partners_api/partners_api_tests/viator_tests.cpp @@ -0,0 +1,77 @@ +#include "testing/testing.hpp" + +#include "partners_api/viator_api.hpp" + +#include <algorithm> +#include <random> +#include <sstream> +#include <vector> + +#include "3party/jansson/myjansson.hpp" + +namespace +{ +UNIT_TEST(Viator_GetTopProducts) +{ + string result; + viator::RawApi::GetTopProducts("684", "USD", 5, result); + + TEST(!result.empty(), ()); + + my::Json root(result.c_str()); + bool success; + FromJSONObjectOptionalField(root.get(), "success", success, false); + TEST(success, ()); +} + +UNIT_TEST(Viator_GetTop5Products) +{ + viator::Api api; + std::string const kSofia = "5630"; + + api.GetTop5Products( + kSofia, "", + [kSofia](std::string const & destId, std::vector<viator::Product> const & products) { + TEST_EQUAL(destId, kSofia, ()); + TEST(!products.empty(), ()); + + testing::StopEventLoop(); + }); + + testing::RunEventLoop(); +} + +UNIT_TEST(Viator_SortProducts) +{ + std::vector<viator::Product> products = + { + {"1", 10.0, 10, "", 10.0, "", "", "", ""}, + {"2", 9.0, 100, "", 200.0, "", "", "", ""}, + {"3", 9.0, 100, "", 100.0, "", "", "", ""}, + {"4", 9.0, 10, "", 200.0, "", "", "", ""}, + {"5", 9.0, 9, "", 300.0, "", "", "", ""}, + {"6", 8.0, 200, "", 400.0, "", "", "", ""}, + {"7", 7.0, 20, "", 200.0, "", "", "", ""}, + {"8", 7.0, 20, "", 1.0, "", "", "", ""}, + {"9", 7.0, 0, "", 300.0, "", "", "", ""}, + {"10", 7.0, 0, "", 1.0, "", "", "", ""} + }; + + for (size_t i = 0 ; i < 1000; ++i) + { + std::shuffle(products.begin(), products.end(), std::minstd_rand(std::minstd_rand::default_seed)); + viator::SortProducts(products); + + TEST_EQUAL(products[0].m_title, "1", ()); + TEST_EQUAL(products[1].m_title, "2", ()); + TEST_EQUAL(products[2].m_title, "3", ()); + TEST_EQUAL(products[3].m_title, "4", ()); + TEST_EQUAL(products[4].m_title, "5", ()); + TEST_EQUAL(products[5].m_title, "6", ()); + TEST_EQUAL(products[6].m_title, "7", ()); + TEST_EQUAL(products[7].m_title, "8", ()); + TEST_EQUAL(products[8].m_title, "9", ()); + TEST_EQUAL(products[9].m_title, "10", ()); + } +} +} // namespace diff --git a/partners_api/viator_api.cpp b/partners_api/viator_api.cpp new file mode 100644 index 0000000000..a08fe9c275 --- /dev/null +++ b/partners_api/viator_api.cpp @@ -0,0 +1,245 @@ +#include "partners_api/viator_api.hpp" + +#include "platform/http_client.hpp" +#include "platform/preferred_languages.hpp" + +#include "coding/multilang_utf8_string.hpp" + +#include "base/logging.hpp" +#include "base/thread.hpp" + +#include <algorithm> +#include <set> +#include <sstream> +#include <unordered_map> + +#include "3party/jansson/myjansson.hpp" + +#include "private.h" + +namespace +{ +using namespace platform; +using namespace viator; + +std::string const kApiUrl = "https://viatorapi.viator.com"; +std::string const kWebUrl = "https://www.partner.viator.com"; + +int8_t GetLang(string const & lang) +{ + return StringUtf8Multilang::GetLangIndex(lang); +} + +using IdsMap = std::unordered_map<int8_t, std::string>; + +IdsMap kApiKeys = +{ + {GetLang("en"), VIATOR_API_KEY_EN}, + {GetLang("de"), VIATOR_API_KEY_DE}, + {GetLang("fr"), VIATOR_API_KEY_FR}, + {GetLang("es"), VIATOR_API_KEY_ES}, + {GetLang("pt"), VIATOR_API_KEY_PT}, + {GetLang("it"), VIATOR_API_KEY_IT}, + {GetLang("nl"), VIATOR_API_KEY_NL}, + {GetLang("sv"), VIATOR_API_KEY_SV}, + {GetLang("ja"), VIATOR_API_KEY_JA} +}; + +IdsMap kAccountIds = +{ + {GetLang("en"), VIATOR_ACCOUNT_ID_EN}, + {GetLang("de"), VIATOR_ACCOUNT_ID_DE}, + {GetLang("fr"), VIATOR_ACCOUNT_ID_FR}, + {GetLang("es"), VIATOR_ACCOUNT_ID_ES}, + {GetLang("pt"), VIATOR_ACCOUNT_ID_PT}, + {GetLang("it"), VIATOR_ACCOUNT_ID_IT}, + {GetLang("nl"), VIATOR_ACCOUNT_ID_NL}, + {GetLang("sv"), VIATOR_ACCOUNT_ID_SV}, + {GetLang("ja"), VIATOR_ACCOUNT_ID_JA} +}; + +std::string GetId(IdsMap const & from) +{ + int8_t lang = GetLang(languages::GetCurrentNorm()); + + auto const it = from.find(lang); + + if (it != from.cend()) + return it->second; + + LOG(LINFO, ("Viator key for language", lang, "is not found, English key will be used.")); + return from.at(StringUtf8Multilang::kEnglishCode); +} + +std::string GetApiKey() +{ + return GetId(kApiKeys); +} + +std::string GetAccountId() +{ + return GetId(kAccountIds); +} + +bool RunSimpleHttpRequest(string const & url, std::string const & bodyData, string & result) +{ + HttpClient request(url); + request.SetHttpMethod("POST"); + + request.SetBodyData(bodyData, "application/json"); + if (request.RunHttpRequest() && !request.WasRedirected() && request.ErrorCode() == 200) + { + result = request.ServerResponse(); + return true; + } + + return false; +} + +std::string MakeSearchProductsRequest(int destId, std::string const & currency, int count) +{ + std::ostringstream os; + // REVIEW_AVG_RATING_D - average traveler rating (high->low). + os << R"({"topX":"1-)" << count << R"(","destId":)" << destId << R"(,"currencyCode":")" + << currency << R"(","sortOrder":"REVIEW_AVG_RATING_D"})"; + + return os.str(); +} + +std::string MakeUrl(std::string const & apiMethod) +{ + std::ostringstream os; + os << kApiUrl << apiMethod << "?apiKey=" << GetApiKey(); + + return os.str(); +} + +bool CheckAnswer(my::Json const & root) +{ + bool success; + FromJSONObjectOptionalField(root.get(), "success", success, false); + + if (!success) + { + std::string errorMessage; + FromJSONObject(root.get(), "errorMessageText", errorMessage); + LOG(LWARNING, ("Viator retrieved unsuccessfull status, error message:", errorMessage)); + return false; + } + + return true; +} + +bool CheckDataArray(json_t const * data) +{ + if (data == nullptr) + return false; + + if (!json_is_array(data)) + return false; + + if (json_array_size(data) <= 0) + return false; + + return true; +} + +void MakeProducts(std::string const & src, std::vector<Product> & products) +{ + products.clear(); + + my::Json root(src.c_str()); + auto const data = json_object_get(root.get(), "data"); + if (!CheckAnswer(root) || !CheckDataArray(data)) + return; + + auto const dataSize = json_array_size(data); + for (size_t i = 0; i < dataSize; ++i) + { + Product product; + auto const item = json_array_get(data, i); + FromJSONObject(item, "shortTitle", product.m_title); + FromJSONObject(item, "rating", product.m_rating); + FromJSONObject(item, "reviewCount", product.m_reviewCount); + FromJSONObject(item, "duration", product.m_duration); + FromJSONObject(item, "price", product.m_price); + FromJSONObject(item, "priceFormatted", product.m_priceFormatted); + FromJSONObject(item, "currencyCode", product.m_currency); + FromJSONObject(item, "thumbnailHiResURL", product.m_photoUrl); + FromJSONObject(item, "webURL", product.m_pageUrl); + products.push_back(move(product)); + } +} +} // namespace + +namespace viator +{ +// static +bool RawApi::GetTopProducts(string const & destId, string const & currency, int count, + string & result) +{ + int dest = 0; + CHECK(strings::to_int(destId, dest), ()); + + return RunSimpleHttpRequest(MakeUrl("/service/search/products"), + MakeSearchProductsRequest(dest, currency, count), result); +} + +// static +std::string Api::GetCityUrl(std::string const & destId, std::string const & name) +{ + std::ostringstream ost; + // The final language and city name will be calculated automatically based on account id and + // destination id. + ost << kWebUrl << "/" << languages::GetCurrentNorm() << "/" << GetAccountId() << "/" << name + << "/d" << destId << "-ttd?activities=all"; + return ost.str(); +} + +void Api::GetTop5Products(std::string const & destId, std::string const & currency, + GetTop5ProductsCallback const & fn) const +{ + std::string curr = currency.empty() ? "USD" : currency; + + threads::SimpleThread([destId, curr, fn]() + { + string result; + if (!RawApi::GetTopProducts(destId, curr, 5, result)) + return fn(destId, {}); + + std::vector<Product> products; + try + { + MakeProducts(result, products); + } + catch (my::Json::Exception const & e) + { + LOG(LERROR, (e.Msg())); + products.clear(); + } + + SortProducts(products); + + fn(destId, products); + }).detach(); +} + +bool operator<(Product const & lhs, Product const & rhs) +{ + return lhs.m_rating < rhs.m_rating || + (lhs.m_rating == rhs.m_rating && lhs.m_reviewCount < rhs.m_reviewCount) || + (lhs.m_reviewCount == rhs.m_reviewCount && lhs.m_price < rhs.m_price); +} + +// Sort by rating (from the best to the worst), +// then by reviews (from the largest to the smallest), +// then by price (from the biggest to the lowest) +void SortProducts(std::vector<Product> & products) +{ + std::multiset<Product> productsSet; + for (auto const & p : products) + productsSet.insert(p); + + std::copy(productsSet.crbegin(), productsSet.crend(), products.begin()); +} +} // namespace viator diff --git a/partners_api/viator_api.hpp b/partners_api/viator_api.hpp new file mode 100644 index 0000000000..1c2edc4986 --- /dev/null +++ b/partners_api/viator_api.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include <functional> +#include <string> + +namespace viator +{ +class RawApi +{ +public: + /// Returns top <count> products for specified city id. + static bool GetTopProducts(std::string const & destId, std::string const & currency, int count, + std::string & result); +}; + +struct Product +{ + std::string m_title; + double m_rating; + int m_reviewCount; + std::string m_duration; + double m_price; + std::string m_priceFormatted; + std::string m_currency; + std::string m_photoUrl; + std::string m_pageUrl; +}; + +using GetTop5ProductsCallback = + std::function<void(std::string const & destId, std::vector<Product> const & products)>; + +class Api +{ +public: + /// Returns web page address for specified city id. + static std::string GetCityUrl(std::string const & destId, std::string const & name); + + /// Returns top-5 products for specified city id. + /// @currency - currency of the price, if empty then USD will be used. + void GetTop5Products(std::string const & destId, std::string const & currency, + GetTop5ProductsCallback const & fn) const; +}; + +bool operator<(Product const & lhs, Product const & rhs); +void SortProducts(std::vector<Product> & products); +} // namespace viator |