mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
The NexusDashboard-parity dashboard (dDashboardServer) and everything built on it on the experimental branch: accounts, characters, properties and moderation tools, permissions shared with in-game slash commands, economy reports, World 3D and property 3D views with client scenery, scheduled events (features, vanity changes, live events, announcements, restarts), vanity files and events, the CDClient browser, the message inspector with saved captures, chat filter tools, community challenges, live ops, the AI moderator helper, and the server-side changes they need. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
224 lines
9.1 KiB
C++
224 lines
9.1 KiB
C++
#include "MySQLDatabase.h"
|
|
|
|
#include "GeneralUtils.h"
|
|
#include "eGameMasterLevel.h"
|
|
#include "json.hpp"
|
|
|
|
std::optional<IAccounts::Info> MySQLDatabase::GetAccountInfo(const std::string_view username) {
|
|
auto result = ExecuteSelect("SELECT id, password, banned, locked, play_key_id, gm_level, mute_expire, ban_expires, ban_reason FROM accounts WHERE name = ? LIMIT 1;", username);
|
|
|
|
if (!result->next()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
IAccounts::Info toReturn;
|
|
toReturn.id = result->getUInt("id");
|
|
toReturn.maxGmLevel = static_cast<eGameMasterLevel>(result->getInt("gm_level"));
|
|
toReturn.bcryptPassword = result->getString("password").c_str();
|
|
toReturn.banned = result->getBoolean("banned");
|
|
toReturn.locked = result->getBoolean("locked");
|
|
toReturn.playKeyId = result->getUInt("play_key_id");
|
|
toReturn.muteExpire = result->getUInt64("mute_expire");
|
|
toReturn.banExpires = result->getInt64("ban_expires");
|
|
toReturn.banReason = result->isNull("ban_reason") ? "" : result->getString("ban_reason").c_str();
|
|
|
|
return toReturn;
|
|
}
|
|
|
|
void MySQLDatabase::UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) {
|
|
ExecuteUpdate("UPDATE accounts SET mute_expire = ? WHERE id = ?;", timeToUnmute, accountId);
|
|
}
|
|
|
|
void MySQLDatabase::UpdateAccountBan(const uint32_t accountId, const bool banned) {
|
|
ExecuteUpdate("UPDATE accounts SET banned = ? WHERE id = ?;", banned, accountId);
|
|
}
|
|
|
|
void MySQLDatabase::UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) {
|
|
ExecuteUpdate("UPDATE accounts SET password = ? WHERE id = ?;", bcryptpassword, accountId);
|
|
}
|
|
|
|
void MySQLDatabase::InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword, const eGameMasterLevel gmLevel) {
|
|
ExecuteInsert("INSERT INTO accounts (name, password, gm_level) VALUES (?, ?, ?);", username, bcryptpassword, static_cast<int32_t>(gmLevel));
|
|
}
|
|
|
|
void MySQLDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) {
|
|
ExecuteUpdate("UPDATE accounts SET gm_level = ? WHERE id = ?;", static_cast<int32_t>(gmLevel), accountId);
|
|
}
|
|
|
|
uint32_t MySQLDatabase::GetAccountCount() {
|
|
auto res = ExecuteSelect("SELECT COUNT(*) as count FROM accounts;");
|
|
return res->next() ? res->getUInt("count") : 0;
|
|
}
|
|
|
|
void MySQLDatabase::RecordFailedAttempt(const uint32_t accountId) {
|
|
ExecuteUpdate("UPDATE accounts SET failed_attempts = failed_attempts + 1 WHERE id = ?;", accountId);
|
|
}
|
|
|
|
void MySQLDatabase::ClearFailedAttempts(const uint32_t accountId) {
|
|
ExecuteUpdate("UPDATE accounts SET failed_attempts = 0, lockout_time = NULL, last_login = NOW() WHERE id = ?;", accountId);
|
|
}
|
|
|
|
void MySQLDatabase::SetLockout(const uint32_t accountId, const int64_t lockoutUntil) {
|
|
ExecuteUpdate("UPDATE accounts SET lockout_time = FROM_UNIXTIME(NULLIF(?, 0)), failed_attempts = 0 WHERE id = ?;", lockoutUntil, accountId);
|
|
}
|
|
|
|
bool MySQLDatabase::IsLockedOut(const uint32_t accountId) {
|
|
auto result = ExecuteSelect("SELECT 1 AS locked FROM accounts WHERE id = ? AND lockout_time IS NOT NULL AND lockout_time > NOW();", accountId);
|
|
return result->next();
|
|
}
|
|
|
|
uint32_t MySQLDatabase::CountActiveAccountsAtGmLevel(const uint8_t gmLevel, const uint32_t excludeAccountId) {
|
|
auto result = ExecuteSelect("SELECT COUNT(*) AS count FROM accounts WHERE gm_level = ? AND id != ? AND banned = 0 AND locked = 0;", static_cast<int32_t>(gmLevel), excludeAccountId);
|
|
return result->next() ? result->getUInt("count") : 0;
|
|
}
|
|
|
|
void MySQLDatabase::SetAccountLocked(const uint32_t accountId, const bool locked) {
|
|
ExecuteUpdate("UPDATE accounts SET locked = ? WHERE id = ?;", locked, accountId);
|
|
}
|
|
|
|
uint8_t MySQLDatabase::GetFailedAttempts(const uint32_t accountId) {
|
|
auto result = ExecuteSelect("SELECT failed_attempts FROM accounts WHERE id = ?;", accountId);
|
|
if (!result->next()) {
|
|
return 0;
|
|
}
|
|
|
|
return result->getUInt("failed_attempts");
|
|
}
|
|
|
|
nlohmann::json MySQLDatabase::GetAccountsTable(uint32_t start, uint32_t length, const std::string_view search, uint32_t orderColumn, bool orderAsc) {
|
|
// A number in the search box also matches IDs exactly (-1 never matches)
|
|
const int64_t searchId = GeneralUtils::TryParse<int64_t>(std::string(search)).value_or(-1);
|
|
// Build base query
|
|
std::string baseQuery = "SELECT id, name, banned, locked, gm_level, mute_expire, created_at FROM accounts";
|
|
std::string whereClause;
|
|
std::string orderClause;
|
|
|
|
// Add search filter if provided
|
|
if (!search.empty()) {
|
|
whereClause = " WHERE (name LIKE CONCAT('%', ?, '%') OR id = ? OR id IN (SELECT account_id FROM charinfo WHERE name LIKE CONCAT('%', ?, '%')))";
|
|
}
|
|
|
|
// Map column indices to database columns
|
|
std::string orderColumnName = "id";
|
|
switch (orderColumn) {
|
|
case 0: orderColumnName = "id"; break;
|
|
case 1: orderColumnName = "name"; break;
|
|
case 2: orderColumnName = "banned"; break;
|
|
case 3: orderColumnName = "locked"; break;
|
|
case 4: orderColumnName = "gm_level"; break;
|
|
case 5: orderColumnName = "mute_expire"; break;
|
|
case 6: orderColumnName = "created_at"; break;
|
|
default: orderColumnName = "id";
|
|
}
|
|
|
|
orderClause = " ORDER BY " + orderColumnName + (orderAsc ? " ASC" : " DESC");
|
|
|
|
// Build the main query
|
|
std::string mainQuery = baseQuery + whereClause + orderClause + " LIMIT ?, ?;";
|
|
|
|
// Get total count
|
|
std::string totalCountQuery = "SELECT COUNT(*) as count FROM accounts;";
|
|
auto totalCountResult = ExecuteSelect(totalCountQuery);
|
|
uint32_t totalRecords = totalCountResult->next() ? totalCountResult->getUInt("count") : 0;
|
|
|
|
// Get filtered count
|
|
uint32_t filteredRecords = totalRecords;
|
|
if (!search.empty()) {
|
|
std::string filteredCountQuery = "SELECT COUNT(*) as count FROM accounts WHERE (name LIKE CONCAT('%', ?, '%') OR id = ? OR id IN (SELECT account_id FROM charinfo WHERE name LIKE CONCAT('%', ?, '%')));";
|
|
auto filteredCountResult = ExecuteSelect(filteredCountQuery, search, searchId, search);
|
|
filteredRecords = filteredCountResult->next() ? filteredCountResult->getUInt("count") : 0;
|
|
}
|
|
|
|
// Execute main query
|
|
auto result = !search.empty()
|
|
? ExecuteSelect(mainQuery, search, searchId, search, start, length)
|
|
: ExecuteSelect(mainQuery, start, length);
|
|
|
|
// Build response JSON
|
|
nlohmann::json accountsArray = nlohmann::json::array();
|
|
|
|
while (result->next()) {
|
|
nlohmann::json account = {
|
|
{"id", result->getUInt("id")},
|
|
{"name", result->getString("name")},
|
|
{"banned", result->getBoolean("banned")},
|
|
{"locked", result->getBoolean("locked")},
|
|
{"gm_level", result->getInt("gm_level")},
|
|
{"mute_expire", result->getUInt64("mute_expire")},
|
|
{"created_at", result->getString("created_at")}
|
|
};
|
|
accountsArray.push_back(account);
|
|
}
|
|
|
|
nlohmann::json response = {
|
|
{"draw", 1},
|
|
{"recordsTotal", totalRecords},
|
|
{"recordsFiltered", filteredRecords},
|
|
{"data", accountsArray}
|
|
};
|
|
|
|
return response;
|
|
}
|
|
|
|
nlohmann::json MySQLDatabase::GetAccountById(uint32_t accountId) {
|
|
try {
|
|
const std::string query = "SELECT id, name, banned, locked, gm_level, mute_expire, created_at, ban_expires, ban_reason FROM accounts WHERE id = ?;";
|
|
auto result = ExecuteSelect(query, accountId);
|
|
|
|
if (!result->next()) {
|
|
return nlohmann::json{{"error", "Account not found"}};
|
|
}
|
|
|
|
nlohmann::json account = {
|
|
{"id", result->getUInt("id")},
|
|
{"name", result->getString("name")},
|
|
{"banned", result->getBoolean("banned")},
|
|
{"locked", result->getBoolean("locked")},
|
|
{"ban_expires", result->getInt64("ban_expires")},
|
|
{"ban_reason", result->isNull("ban_reason") ? std::string() : std::string(result->getString("ban_reason").c_str())},
|
|
{"gm_level", result->getInt("gm_level")},
|
|
{"mute_expire", result->getUInt64("mute_expire")},
|
|
{"created_at", result->getString("created_at")}
|
|
};
|
|
|
|
return account;
|
|
} catch (const sql::SQLException& e) {
|
|
LOG_DEBUG("SQL Error: %s", e.what());
|
|
return nlohmann::json{{"error", "Database error"}};
|
|
}
|
|
}
|
|
|
|
void MySQLDatabase::DeleteAccount(const uint32_t accountId) {
|
|
// All or nothing: a failure part way leaves the account as it was
|
|
DatabaseTransaction transaction(*this);
|
|
std::vector<LWOOBJID> characters;
|
|
{
|
|
auto result = ExecuteSelect("SELECT id FROM charinfo WHERE account_id = ?;", accountId);
|
|
while (result->next()) characters.push_back(result->getInt64("id"));
|
|
}
|
|
for (const auto characterId : characters) {
|
|
DeleteCharacter(characterId);
|
|
ExecuteDelete("DELETE FROM character_snapshots WHERE character_id = ?;", characterId);
|
|
}
|
|
// Rows about the account itself. Audit log, chat log and reports stay as the record of what happened.
|
|
for (const auto* table : { "account_tokens", "account_recovery_codes", "account_notes", "account_strikes", "account_login_addresses",
|
|
"dashboard_preferences", "accounts_rewardcodes", "player_cheat_detections" }) {
|
|
ExecuteDelete(std::string("DELETE FROM ") + table + " WHERE account_id = ?;", accountId);
|
|
}
|
|
ExecuteDelete("DELETE FROM accounts WHERE id = ?;", accountId);
|
|
transaction.Commit();
|
|
}
|
|
|
|
nlohmann::json MySQLDatabase::GetAccountCharacters(uint32_t accountId) {
|
|
auto result = ExecuteSelect("SELECT id, name, last_login FROM charinfo WHERE account_id = ? ORDER BY last_login DESC;", accountId);
|
|
nlohmann::json chars = nlohmann::json::array();
|
|
while (result->next()) {
|
|
chars.push_back({
|
|
{"id", std::to_string(result->getInt64("id"))},
|
|
{"name", result->getString("name")},
|
|
{"last_login", result->getUInt64("last_login")}
|
|
});
|
|
}
|
|
return chars;
|
|
}
|