#include "postgresHandler.hpp" #include "hearthstoneAPIHandler.hpp" #include #include #include #include #include #include #include #define KEY_ID 1 // TODO: create a list/array which contains CardTypeHandlerInfo struct objects // and fill these info in a single function struct CardTypeHandlerInfo { std::string aCardType; std::string aCardRegex; std::function aJSONToSQLConverter; }; const std::map aCardValueTypeToRegex = { {"INT", "[0-9]+"}, {"STRING", "\"[^\"?]*\""}, {"LIST_OF_INT", "\\[[0-9,]*\\]"}, {"NULL", "null"}, {"BOOL", "true|false"}}; const pqxx::result executeQuery(pqxx::work &aTransaction, std::string_view aQuery) { return aTransaction.exec(aQuery); } std::ostream &operator<<(std::ostream &aOstream, const pqxx::result &kResult) { const int kNbColumnsInResult = kResult.columns(); aOstream << "| "; for (const auto &kResultRow : kResult) { for (int i = 0; i < kNbColumnsInResult; i++) { aOstream << kResultRow[i].c_str() << " | "; } } return aOstream; } const unsigned int getTypeIndexOfRegexIterator(const std::sregex_iterator &kSregexIt) { // TODO: remove hardcoded variable because it is... weird. // The input should already be the "value" part? const unsigned int offset = 2; for (size_t aCardInfoType = 0; aCardInfoType < aCardValueTypeToRegex.size(); aCardInfoType++) { if ((*kSregexIt)[aCardInfoType] != "") { return offset + aCardInfoType; } } throw std::invalid_argument("regex iterator input is invalid"); } const std::string format_json_value_to_sql(const std::string &kTypeOfValue, const std::string &kValueFromJSON) { // TODO: show an error if a type is no longer here, // to avoid having useless conditions // FIXME: it does not scale well, a bit ugly... should be a single line if (kTypeOfValue == "BOOL" || kTypeOfValue == "INT") { return kValueFromJSON; } else if (kTypeOfValue == "STRING") { std::string aCopyOfJSONValue = kValueFromJSON; *aCopyOfJSONValue.begin() = '\''; *std::prev(aCopyOfJSONValue.end()) = '\''; return aCopyOfJSONValue; } else if (kTypeOfValue == "NULL") { return "NULL"; } else if (kTypeOfValue == "LIST_OF_INT") { std::string aCopyOfJSONValue = kValueFromJSON; aCopyOfJSONValue.append("\'"); aCopyOfJSONValue.insert(aCopyOfJSONValue.begin(), '\''); return aCopyOfJSONValue; } else { throw std::invalid_argument("ERROR: type of value is invalid"); } } std::string format_key(const std::string &key) { std::string key_copy(key); std::stack uppercase_indexes; for (size_t i = 0; i < key.length(); i++) { if (std::isupper(key[i])) { uppercase_indexes.push(i); } } while (!uppercase_indexes.empty()) { size_t top_index = uppercase_indexes.top(); uppercase_indexes.pop(); key_copy[top_index] = std::tolower(key_copy[top_index]); key_copy.insert(std::next(key_copy.begin(), top_index), '_'); } return key_copy; } static void add_single_card_to_database(const std::string &t_cards, pqxx::work &t_tx, const std::regex &key_regex, const std::string::size_type &id_pos, const std::string::size_type &closing_bracket_pos) { auto words_begin = std::sregex_iterator( std::next(t_cards.begin(), id_pos), std::next(t_cards.begin(), closing_bracket_pos), key_regex); auto words_end = std::sregex_iterator(); if (words_begin != words_end) { std::string insert_columns("INSERT INTO cards ("); std::string values(" VALUES ("); for (std::sregex_iterator kSregexIt = words_begin; kSregexIt != words_end; ++kSregexIt) { insert_columns.append(format_key((*kSregexIt)[KEY_ID].str()) + ","); // key const unsigned int kTypeIndexOfValue = getTypeIndexOfRegexIterator(kSregexIt); const std::string kTypeNameOfValue = std::next(aCardValueTypeToRegex.begin(), kTypeIndexOfValue) ->first; values.append( format_json_value_to_sql( kTypeNameOfValue, (*kSregexIt)[kTypeIndexOfValue].str()) + ","); } *std::prev(insert_columns.end()) = ')'; *std::prev(values.end()) = ')'; insert_columns.append(values); insert_columns.append(" ON CONFLICT (id) DO NOTHING;"); executeQuery(t_tx, insert_columns); } } const std::regex keyValueJSONRegex() { std::string aRegexString = "\"([^\"?]+)\":("; for (auto &[fieldName, regex] : aCardValueTypeToRegex) { aRegexString += "(" + regex + ")|"; } aRegexString[aRegexString.length() - 1] = ')'; return std::regex(aRegexString); } const std::string::size_type startPosOfCardJSON(const std::string &kCardsPageJSON, const std::string::size_type kSearchStartPos = 0) { return kCardsPageJSON.find("{\"id", kSearchStartPos); } const std::string::size_type endPosOfCardJSON(const std::string &kCardsPageJSON, const std::string::size_type kSearchStartPos = 0) { return kCardsPageJSON.find("}", kSearchStartPos); } void addCardsToDatabase(HearthstoneAPIHandler &aAPIIntermediate, pqxx::work &aTransaction, const std::string &kURLOfCardsPage) { const std::regex aKeyValueJSONRegex = keyValueJSONRegex(); const std::string &kCardsPageJSON = aAPIIntermediate.get_cards(kURLOfCardsPage); std::string::size_type aCardStartPos = startPosOfCardJSON(kCardsPageJSON); std::string::size_type aCardEndPos = endPosOfCardJSON(kCardsPageJSON); while (aCardStartPos != std::string::npos && aCardEndPos != std::string::npos) { add_single_card_to_database(kCardsPageJSON, aTransaction, aKeyValueJSONRegex, aCardStartPos, aCardEndPos); aCardStartPos = startPosOfCardJSON(kCardsPageJSON, aCardStartPos + 1); aCardEndPos = endPosOfCardJSON(kCardsPageJSON, aCardEndPos + 1); } } void clearCardDatabase(pqxx::work &tx) { executeQuery(tx, "DELETE FROM cards"); } void refresh_cards_table() { try { pqxx::connection cx{ "postgresql://aotrix@localhost:5432/hearthstonegameinfo"}; pqxx::work tx{cx}; HearthstoneAPIHandler aAPIIntermediate; std::string cards = aAPIIntermediate.get_cards("/?locale=fr_FR&gameMode=constructed"); clearCardDatabase(tx); addCardsToDatabase(aAPIIntermediate, tx, "/?locale=fr_FR&gameMode=constructed"); std::regex key_regex("\"pageCount\":([0-9]+)"); auto nb_pages_begin = std::sregex_iterator(cards.begin(), cards.end(), key_regex); auto nb_pages_end = std::sregex_iterator(); if (nb_pages_begin != nb_pages_end) { uint32_t nb_pages = std::stoi((*nb_pages_begin)[1].str()); std::cout << "number of pages: " << nb_pages << std::endl; // FIXME: [Process exited 139] // for (int i = 2; i < nb_pages; i++) { for (uint32_t i = 2; i < 3; i++) { const std::string kCardsPageURL{std::format( "/?locale=fr_FR&gameMode=constructed&page={}", i)}; addCardsToDatabase(aAPIIntermediate, tx, kCardsPageURL); } } std::cout << "number of cards in database: " << executeQuery(tx, "SELECT COUNT(*) FROM cards") << std::endl; tx.commit(); } catch (pqxx::sql_error const &e) { std::cerr << "SQL error: " << e.what() << std::endl; std::cerr << "Query was: " << e.query() << std::endl; } catch (std::exception const &e) { std::cerr << "Error: " << e.what() << std::endl; } }