#include "DatabaseHandler.h" #include #include using namespace std; using namespace std::chrono; using namespace license; using namespace license::server::database; using namespace sql; DatabaseHandler::DatabaseHandler(sql::SqlManager* database) : database(database){ this->id_cache = make_shared(this); } DatabaseHandler::~DatabaseHandler() { } /* LicenseType type; std::string username; std::string first_name; std::string last_name; std::string email; std::chrono::system_clock::time_point start; std::chrono::system_clock::time_point end; std::chrono::system_clock::time_point creation; */ #define CTBL(cmd) \ res = command(this->database, cmd).execute(); \ if(!res) { \ error = "Could not setup tables! (" + res.fmtStr() + ")"; \ return false; \ } #define CIDX(cmd) \ res = command(this->database, cmd).execute(); \ if(!res && res.msg().find("Duplicate") == string::npos && res.msg().find("exist") == string::npos) { \ error = "Could not setup indexes! (" + res.fmtStr() + ")"; \ return false; \ } #define SET_VERSION(ver) \ version = ver; \ sql::command(this->database, "UPDATE `general` SET `value` = :version WHERE `key` = 'version'", variable{":version", version}).execute(); bool DatabaseHandler::setup(std::string& error) { result res; int version = -1; sql::command(this->database, "SELECT `value` FROM `general` WHERE `key` = 'version'").query([](int* version, int lnegth, string* values, string* names) { *version = stoll(values[0]); return 0; }, &version); switch (version) { case -1: CTBL("CREATE TABLE IF NOT EXISTS `license` (`key` BINARY(64) NOT NULL PRIMARY KEY, `type` INT, `deleted` BOOL, `issuer` TEXT)"); CTBL("CREATE TABLE IF NOT EXISTS `license_info` (`key` BINARY(64) NOT NULL PRIMARY KEY, `username` VARCHAR(128), `first_name` TEXT, `last_name` TEXT, `email` TEXT, `begin` BIGINT, `end` BIGINT, `generated` BIGINT)"); CTBL("CREATE TABLE IF NOT EXISTS `license_request` (`key` BINARY(64) NOT NULL, `ip` TEXT, `timestamp` BIGINT, `result` INT)"); CTBL("CREATE TABLE IF NOT EXISTS `general` (`key` VARCHAR(64), `value` TEXT)"); CTBL("INSERT INTO `general` (`key`, `value`) VALUES ('version', '0');"); SET_VERSION(0); case 0: logMessage(LOG_GENERAL, "Updating database! To version 1"); CTBL("CREATE TABLE IF NOT EXISTS `history_speach` (`keyId` INT, `timestamp` BIGINT, `total` BIGINT, `dead` BIGINT, `online` BIGINT, `varianz` BIGINT)"); CTBL("CREATE TABLE IF NOT EXISTS `history_online` (`keyId` INT, `timestamp` BIGINT, `server` INT, `clients` INT, `web` INT, `music` INT, `queries` INT)"); CIDX("CREATE INDEX `license_key` ON `license` (`key`)"); CIDX("CREATE INDEX `license_info_key` ON `license_info` (`key`)"); CTBL("ALTER TABLE `license` DROP PRIMARY KEY, ADD `keyId` INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST;"); CTBL("ALTER TABLE `license_info` DROP PRIMARY KEY, ADD `keyId` INT NOT NULL PRIMARY KEY AUTO_INCREMENT FIRST;"); //Fixing key id's CTBL("UPDATE `license_info` LEFT JOIN `license` ON `license`.`key` = `license_info`.`key` SET `license_info`.`keyId` = `license`.`keyId`"); //Deleting the key blob and removing auto CTBL("ALTER TABLE `license_info` CHANGE `keyId` `keyId` INT NOT NULL UNIQUE, DROP COLUMN `key`"); //Fixing request table CTBL("ALTER TABLE license_request ADD COLUMN `keyId` INT(0) NOT NULL;"); CTBL("UPDATE license_request LEFT JOIN license ON license.key = license_request.key SET license_request.keyId = license.keyId;"); CTBL("ALTER TABLE license_request DROP COLUMN `key`;"); //IDK why but i failed stuff :D CTBL("ALTER TABLE license DROP PRIMARY KEY, CHANGE `keyId` `keyId` INT NOT NULL AUTO_INCREMENT PRIMARY KEY"); SET_VERSION(1); case 1: CTBL("ALTER TABLE history_online ADD COLUMN `ip` TEXT AFTER `keyId`;"); CTBL("ALTER TABLE history_speach ADD COLUMN `ip` TEXT AFTER `keyId`;"); SET_VERSION(2); case 2: CTBL("CREATE TABLE `history_version` (`keyId` INT, `ip` VARCHAR(64), `timestamp` BIGINT, `version` VARCHAR(126));"); SET_VERSION(3); case 3: /* CTBL("CREATE TABLE `save_history_online` AS SELECT * FROM `history_online`;"); CTBL("CREATE TABLE `save_history_speach` AS SELECT * FROM `history_speach`;"); CTBL("CREATE TABLE `save_history_version` AS SELECT * FROM `history_version`;"); */ CTBL("ALTER TABLE `history_online` CHANGE `ip` `unique_id` VARCHAR(64) NOT NULL;"); CTBL("ALTER TABLE `history_speach` CHANGE `ip` `unique_id` VARCHAR(64) NOT NULL;"); CTBL("ALTER TABLE `history_version` CHANGE `ip` `unique_id` VARCHAR(64) NOT NULL;"); CTBL("ALTER TABLE `license_request` ADD `unique_id` VARCHAR(64) NOT NULL AFTER `ip`;"); SET_VERSION(4); case 4: CTBL("CREATE TABLE IF NOT EXISTS `users` (`username` VARCHAR(64) NOT NULL PRIMARY KEY, `password_hash` VARCHAR(128), `status` INT, `owner` VARCHAR(64))"); SET_VERSION(5); case 5: CTBL("CREATE TABLE `license_upgrades` (`upgrade_id` INT PRIMARY KEY NOT NULL, `old_key_id` INT, `new_key_id` INT, `timestamp_begin` BIGINT, `timestamp_end` BIGINT, `valid` INT, `use_count` INT, `license` BLOB);"); CTBL("ALTER TABLE `license_upgrades` ADD INDEX(`old_key_id`)"); CTBL("ALTER TABLE `license` ADD INDEX(`keyId`);"); CTBL("ALTER TABLE `license` ADD COLUMN `upgrade_id` INT DEFAULT 0;"); SET_VERSION(6); case 6: CTBL("CREATE TABLE license_upgrade_log (`upgrade_id` INT, `timestamp` INT, `unique_id` VARCHAR(64), `server_ip` INT, `succeeded` TINYINT);"); CIDX("CREATE INDEX `upgrade_id_timestamp` ON `license_upgrade_log` (`upgrade_id`, `timestamp`)"); SET_VERSION(7); default:; } return true; } bool DatabaseHandler::register_license(const std::string& key, const shared_ptr& info, const std::string& issuer) { result res; res = command(this->database, "INSERT INTO `license` (`key`, `type`, `deleted`, `issuer`) VALUES (:key, :type, :deleted, :issuer)", variable{":key", key}, variable{":type", (uint32_t) info->type}, variable{":deleted", false}, variable{":issuer", issuer}).execute(); if(!res) { logError(LOG_GENERAL, "Could not register new license (" + res.fmtStr() + ")"); return false; } auto keyId = this->id_cache->get_key_id_from_key(key); if(keyId == 0) return false; res = command(this->database, "INSERT INTO `license_info` (`keyId`, `username`, `first_name`, `last_name`, `email`, `begin`, `end`, `generated`) VALUES (:key, :username, :first_name, :last_name, :email, :begin, :end, :generated)", variable{":key", keyId}, variable{":username", info->username}, variable{":first_name", info->first_name}, variable{":last_name", info->last_name}, variable{":email", info->email}, variable{":generated", duration_cast(info->creation.time_since_epoch()).count()}, variable{":begin", duration_cast(info->start.time_since_epoch()).count()}, variable{":end", duration_cast(info->end.time_since_epoch()).count()} ).execute(); if(!res) { logError(LOG_GENERAL, "Could not register new license info (" + res.fmtStr() + ")"); return false; } return true; } bool DatabaseHandler::delete_license(const std::string& key, bool full) { if(full) { auto keyId = this->id_cache->get_key_id_from_key(key); if(keyId == 0) return false; //Never exists auto res = command(this->database, "DELETE FROM `license` WHERE `key` = :key", variable{":key", key}).execute(); if(!res) logError(LOG_GENERAL, "Could not delete license (" + res.fmtStr() + ")"); res = command(this->database, "DELETE FROM `license_info` WHERE `keyId` = :key", variable{":keyId", keyId}).execute(); if(!res) logError(LOG_GENERAL, "Could not delete license (" + res.fmtStr() + ")"); return !!res; } else { auto res = command(this->database, "UPDATE `license` SET `deleted` = :true WHERE `key` = :key", variable{":true", true}, variable{":key", key}).execute(); if(!res) logError(LOG_GENERAL, "Could not delete license (" + res.fmtStr() + ")"); return !!res; } } bool DatabaseHandler::validLicenseKey(const std::string& key) { bool valid = false; auto res = command(this->database, "SELECT * FROM `license` WHERE `key` = :key AND `deleted` = :false LIMIT 1", variable{":false", false}, variable{":key", key}).query([](bool* flag, int, char**, char**) { *flag = true; return 0; }, &valid); if(!res) logError(LOG_GENERAL, "Could not validate license (" + res.fmtStr() + ")"); return !!res && valid; } inline std::map> query_license(SqlManager* mgr, const std::string& key, int offset, int length) { std::map> result; auto query = string() + "SELECT `key`, `username`, `first_name`, `last_name`, `email`, `begin`, `end`, `generated`, `deleted`, `upgrade_id` FROM `license_info` INNER JOIN `license` ON `license_info`.`keyId` = `license`.`keyId`"; if(!key.empty()) query += "WHERE `key` = :key"; else query += "WHERE `deleted` = :false"; if(offset > 0 || length > 0) query += " LIMIT " + to_string(offset) + ", " + to_string(length); logTrace(LOG_GENERAL, "Executing query {}", query); auto res = command(mgr, query, variable{":key", key}, variable{":false", false}).query([](std::map>* list, int length, std::string* values, std::string* names) { auto info = make_shared(); info->deleted = false; string k; for(int index = 0; index < length; index++) { try { if(names[index] == "username") info->username = values[index]; else if(names[index] == "first_name") info->first_name = values[index]; else if(names[index] == "key") k = values[index]; else if(names[index] == "last_name") info->last_name = values[index]; else if(names[index] == "email") info->email = values[index]; else if(names[index] == "begin") info->start = system_clock::time_point() + milliseconds(stoll(values[index])); else if(names[index] == "end") info->end = system_clock::time_point() + milliseconds(stoll(values[index])); else if(names[index] == "generated") info->creation = system_clock::time_point() + milliseconds(stoll(values[index])); else if(names[index] == "deleted") info->deleted = values[index] == "1" || values[index] == "true"; else if(names[index] == "upgrade_id") info->upgrade_id = std::stol(values[index]); else logError(LOG_GENERAL, "Unknown field {}", names[index]); } catch (std::exception& ex) { logError(LOG_GENERAL, "Failed to parse field {} ({}). Message: {}", names[index], values[index], ex.what()); return 1; } } (*list)[k] = info; return 0; }, &result); logTrace(LOG_GENERAL, "Query returned {} results", result.size()); if(!res) logError(LOG_GENERAL, "Could not query license (" + res.fmtStr() + ")"); return result; } std::shared_ptr DatabaseHandler::query_license_info(const std::string& key) { auto result = query_license(this->database, key, 0, 0); if(result.empty()) return nullptr; return result.begin()->second; } std::map> DatabaseHandler::list_licenses(int offset, int limit) { return query_license(this->database, "", offset, limit); } bool DatabaseHandler::logRequest(const std::string& key, const std::string& unique_id, const std::string& ip, const std::string& version, int state) { result res; auto keyId = this->id_cache->get_key_id_from_key(key); if(keyId == 0) { logError(LOG_GENERAL, "Failed to log license request (could not resolve key id)"); return false; } auto timestamp = duration_cast(system_clock::now().time_since_epoch()).count(); //SELECT * FROM `license_info` WHERE `keyId` IN (SELECT `keyId` FROM `license` WHERE `key` = '000') res = command(this->database, "INSERT INTO `license_request` (`keyId`, `ip`, `unique_id`, `timestamp`, `result`) VALUES (:key, :ip, :unique_id, :timestamp, :result)", variable{":key", keyId}, variable{":ip", ip}, variable{":timestamp", timestamp}, variable{":unique_id", unique_id}, variable{":result", state}).execute(); if(!res) { logError(LOG_GENERAL, "Could not log license validation (" + res.fmtStr() + ")"); return false; } { //Log version res = command(this->database, "INSERT INTO `history_version`(`keyId`, `unique_id`, `timestamp`, `version`) VALUES (:key, :unique_id, :time, :version)", variable{":key", keyId}, variable{":time", timestamp}, variable{":unique_id", unique_id}, variable{":version", version} ).execute(); if(!res) logError(LOG_GENERAL, "Could not log license version statistic (" + res.fmtStr() + ")"); res = {}; } return true; } bool DatabaseHandler::logStatistic(const std::string &key, const std::string& unique_id, const std::string &ip, const ts::proto::license::PropertyUpdateRequest &data) { result res; auto keyId = this->id_cache->get_key_id_from_key(key); if(keyId == 0) return false; auto time = duration_cast(system_clock::now().time_since_epoch()).count(); { //Log online res = command(this->database, "INSERT INTO `history_online`(`keyId`, `unique_id`, `timestamp`, `server`, `clients`, `web`, `music`, `queries`) VALUES (:key, :unique_id, :time, :server, :client, :web, :music, :query)", variable{":key", keyId}, variable{":unique_id", unique_id}, variable{":time", time}, variable{":server", data.servers_online()}, variable{":client", data.clients_online()}, variable{":web", data.web_clients_online()}, variable{":music", data.bots_online()}, variable{":query", data.queries_online()} ).execute(); if(!res) logError(LOG_GENERAL, "Could not log license statistics (" + res.fmtStr() + ")"); res = {}; } //SELECT * FROM `license_info` WHERE `keyId` IN (SELECT `keyId` FROM `license` WHERE `key` = '000') { res = command(this->database, "INSERT INTO `history_speach`(`keyId`, `unique_id`, `timestamp`, `total`, `dead`, `online`, `varianz`) VALUES (:key, :unique_id, :time, :total, :dead, :online, :varianz)", variable{":key", keyId}, variable{":unique_id", unique_id}, variable{":time", time}, variable{":total", data.speach_total()}, variable{":dead", data.speach_dead()}, variable{":online", data.speach_online()}, variable{":varianz", data.speach_varianz()} ).execute(); if(!res) logError(LOG_GENERAL, "Could not log license statistics (" + res.fmtStr() + ")"); res = {}; } return true; } std::shared_ptr DatabaseHandler::list_statistics_user(const system_clock::time_point &begin, const system_clock::time_point &end, const milliseconds &interval) { auto timeout = hours(2) + minutes(10); //TODO timeout configurable? /* initialize the result */ auto allocated_records = (size_t) floor((end - begin) / interval) + 1; auto _result = (UserHistory*) malloc(sizeof(UserHistory) + sizeof(GlobalUserStatistics) * allocated_records); new (_result) UserHistory(); auto result = shared_ptr(_result, [](UserHistory* ptr){ ptr->~UserHistory(); free(ptr); }); result->record_count = 0; result->begin = begin; result->end = end; result->interval = interval; memset(&result->history[0], 0, allocated_records * sizeof(GlobalUserStatistics)); auto info = &_result->history[0]; /* temp db variables */ map server_statistics; bool have_key, have_uid; DatabaseHandler::DatabaseUserStatistics temp_stats; /* temp struct for stats parsing */ DatabaseHandler::DatabaseUserStatistics* stats_ptr; /* pointer to the target stats */ std::chrono::system_clock::time_point current_timestamp = begin + interval; /* upper limit of the current interval */ auto state = command(this->database, "SELECT * FROM `history_online` WHERE `timestamp` >= :timestamp_start AND `timestamp` <= :timestamp_end ORDER BY `timestamp` ASC", variable{":timestamp_start", duration_cast(begin.time_since_epoch() - timeout).count()}, variable{":timestamp_end", duration_cast(end.time_since_epoch()).count()} ).query([&](int columns, std::string* values, std::string* names) { /* find the user statistic ptr */ { size_t key_id = 0; std::string unique_id; have_key = false; have_uid = false; for(int index = 0; index < columns; index++) { if(names[index] == "keyId") { key_id = strtol(values[index].c_str(), nullptr, 10); if(key_id == 0) return 0; /* invalid key id */ have_key = true; if(have_key && have_uid) goto process_tag; } else if(names[index] == "unique_id") { unique_id = values[index]; have_uid = true; if(have_key && have_uid) goto process_tag; } } return 0; /* key or uid haven't been found */ process_tag: stats_ptr = &server_statistics[to_string(key_id) + "_" + unique_id]; } for(int index = 0; index < columns; index++) { if(names[index] == "timestamp") temp_stats.timestamp = system_clock::time_point() + milliseconds(stoll(values[index])); else if(names[index] == "server") temp_stats.servers_online = strtol(values[index].c_str(), nullptr, 10); else if(names[index] == "clients") temp_stats.clients_online = strtol(values[index].c_str(), nullptr, 10); else if(names[index] == "web") temp_stats.web_clients_online = strtol(values[index].c_str(), nullptr, 10); else if(names[index] == "music") temp_stats.bots_online = strtol(values[index].c_str(), nullptr, 10); else if(names[index] == "queries") temp_stats.queries_online = strtol(values[index].c_str(), nullptr, 10); } /* because the query could only be oldest to newest */ while(temp_stats.timestamp > current_timestamp) { assert(_result->record_count < allocated_records); /* ensure we write to valid memory */ auto min_timestamp = current_timestamp - timeout; for(auto& server : server_statistics) { auto& second = server.second; if(second.timestamp < min_timestamp || second.timestamp.time_since_epoch().count() == 0) continue; /* last server request is too old to be counted */ info->instance_online++; info->queries_online += second.queries_online; info->bots_online += second.bots_online; info->web_clients_online += second.web_clients_online; info->clients_online += second.clients_online; info->servers_online += second.servers_online; } info++; _result->record_count++; current_timestamp += interval; /* lets gather for the next interval */ } /* write the "new" statistic */ memcpy(stats_ptr, &temp_stats, sizeof(temp_stats)); return 0; }); if(!state) { logError(LOG_GENERAL, "Could not read license statistics (" + state.fmtStr() + ")"); return {}; } /* flush the last record */ do { assert(_result->record_count < allocated_records); /* ensure we write to valid memory */ auto min_timestamp = current_timestamp - timeout; for(auto& server : server_statistics) { auto& second = server.second; if(second.timestamp < min_timestamp) continue; /* last server request is too old to be counted */ info->instance_online++; info->queries_online += second.queries_online; info->bots_online += second.bots_online; info->web_clients_online += second.web_clients_online; info->clients_online += second.clients_online; info->servers_online += second.servers_online; } info++; _result->record_count++; current_timestamp += interval; } while(current_timestamp < end); return result; } std::deque> DatabaseHandler::list_statistics_version(const std::chrono::system_clock::time_point &begin, const std::chrono::system_clock::time_point &end, const std::chrono::milliseconds &interval) { map>> server_statistics; auto timeout = hours(2) + minutes(10); //TODO timeout configurable? auto state = command(this->database, "SELECT * FROM `history_version` WHERE `timestamp` >= :timestamp_start AND `timestamp` <= :timestamp_end ORDER BY `timestamp` ASC", variable{":timestamp_start", duration_cast(begin.time_since_epoch() - timeout).count()}, variable{":timestamp_end", duration_cast(end.time_since_epoch()).count()} ).query([&server_statistics](int columns, std::string* values, std::string* names){ size_t key_id = 0; std::string unique_id; auto info = make_unique(); for(int index = 0; index < columns; index++) { try { if(names[index] == "keyId") key_id = stoull(values[index]); else if(names[index] == "unique_id") unique_id = values[index]; else if(names[index] == "timestamp") info->timestamp = system_clock::time_point() + milliseconds(stoll(values[index])); else if(names[index] == "version") info->versions[values[index]] = 1; } catch (std::exception& ex) { logError(LOG_GENERAL, "Failed to parse column " + names[index] + " => " + ex.what() + " (Value: " + values[index] + ")"); return 0; } } if(key_id == 0) return 0; server_statistics[to_string(key_id) + "_" + unique_id].push_back(std::move(info)); return 0; }); if(!state) { logError(LOG_GENERAL, "Could not read license statistics (" + state.fmtStr() + ")"); return {}; } std::deque> result; system_clock::time_point current_timestamp = begin; while(current_timestamp <= end) { auto info = make_unique(); info->timestamp = current_timestamp; for(auto& server : server_statistics) { while(!server.second.empty()) { auto& first = *server.second.begin(); if(first->timestamp > current_timestamp) break; //Entry within the future if(first->timestamp + timeout < current_timestamp) { //Entry within the past server.second.pop_front(); continue; } if(server.second.size() > 1) { auto& second = *(server.second.begin() + 1); if(second->timestamp <= current_timestamp) { server.second.pop_front(); //The next entry is more up 2 date continue; } } for(const auto& entry : first->versions) info->versions[entry.first] += entry.second; break; } } result.push_back(std::move(info)); current_timestamp += interval; } return result; } bool DatabaseHandler::register_license_upgrade(license_key_id_t old_key_id, license_key_id_t new_key_id, const std::chrono::system_clock::time_point &begin_timestamp, const std::chrono::system_clock::time_point &end_timestamp, const std::string &license_key) { auto upgrade_id = std::chrono::system_clock::now().time_since_epoch().count(); auto sql_result = sql::command(this->sql(), "INSERT INTO `license_upgrades` (`upgrade_id`, `old_key_id`, `new_key_id`, `timestamp_begin`, `timestamp_end`, `valid`, `use_count`, `license`) VALUES" "(:upgrade_id, :old_key_id, :new_key_id, :timestamp_begin, :timestamp_end, 1, 0, :license)", variable{":upgrade_id", upgrade_id}, variable{":old_key_id", old_key_id}, variable{":new_key_id", new_key_id}, variable{":timestamp_begin", std::chrono::duration_cast(begin_timestamp.time_since_epoch()).count()}, variable{":timestamp_end", std::chrono::duration_cast(end_timestamp.time_since_epoch()).count()}, variable{":license", base64::decode(license_key)}).execute(); if(!sql_result) { logError(LOG_GENERAL, "Failed to insert license upgrade: {}", sql_result.fmtStr()); return false; } sql_result = sql::command(this->sql(), "UPDATE `license` SET `upgrade_id` = :upgrade_id WHERE `keyId` = :key_id", variable{":upgrade_id", upgrade_id}, variable{":key_id", old_key_id}).execute(); if(!sql_result) { logError(LOG_GENERAL, "Failed to set license upgrade: {}", sql_result.fmtStr()); return false; } return true; } std::unique_ptr DatabaseHandler::query_license_upgrade(upgrade_id_t id) { std::unique_ptr result{}; auto sql_result = sql::command(this->sql(), "SELECT `upgrade_id`, `old_key_id`, `new_key_id`, `timestamp_begin`, `timestamp_end`, `valid`, `use_count`, `license` FROM `license_upgrades` WHERE `upgrade_id` = :upgrade_id LIMIT 1", variable{":upgrade_id", id}).query([&](int length, std::string* values, std::string* names) { result = std::make_unique(); for(size_t index = 0; index < length; index++) { try { if(names[index] == "upgrade_id") result->upgrade_id = std::stoull(values[index]); else if(names[index] == "old_key_id") result->old_license_key_id = std::stoull(values[index]); else if(names[index] == "new_key_id") result->new_license_key_id = std::stoull(values[index]); else if(names[index] == "timestamp_begin") result->begin_timestamp = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{std::stoull(values[index])}; else if(names[index] == "timestamp_end") result->end_timestamp = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{std::stoull(values[index])}; else if(names[index] == "use_count") result->update_count = std::stoull(values[index]); else if(names[index] == "valid") result->valid = std::stoull(values[index]) > 0; else if(names[index] == "license") result->license_key = base64::encode(values[index]); } catch(std::exception& ex) { result = nullptr; logWarning(LOG_GENERAL, "Failed to parse column {} for upgrade id {}. (Value: {})", names[index], id, values[index]); return; } } }); if(!sql_result) { logWarning(LOG_GENERAL, "Failed to query license upgrade info for upgrade {}: {}", id, sql_result.fmtStr()); return nullptr; } return result; } void DatabaseHandler::log_license_upgrade_attempt(upgrade_id_t upgrade_id, bool succeeded, const std::string &unique_id, const std::string &ip) { auto result = sql::command(this->sql(), "INSERT INTO `license_upgrade_log` (`upgrade_id`, `timestamp`, `unique_id`, `server_ip`, `succeeded`) VALUES (:upgrade_id, :timestamp, :unique_id, :server_ip, :succeeded);", variable{":upgrade_id", upgrade_id}, variable{":timestamp", std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()}, variable{":unique_id", unique_id}, variable{":server_ip", ip}, variable{":succeeded", succeeded}).execute(); if(!result) logWarning(LOG_GENERAL, "Failed to insert upgrade log into database ({})", result.fmtStr()); if(succeeded) { result = sql::command(this->sql(), "UPDATE `license_upgrades` SET `use_count` = `use_count` + 1 WHERE `upgrade_id` = :upgrade_id", variable{":upgrade_id", upgrade_id}).execute(); if(!result) logWarning(LOG_GENERAL, "Failed to increase upgrade use count MySQL ({})", result.fmtStr()); } }