Add dashboard audit log and configuration management

- Implemented dashboard audit logging with InsertAuditLog, GetRecentAuditLogs, GetAuditLogsByIP, and CleanupOldAuditLogs methods.
- Created dashboard configuration management with GetDashboardConfig and SetDashboardConfig methods.
- Added new tables for dashboard_audit_log and dashboard_config in both MySQL and SQLite migrations.
- Updated CMakeLists to include Crow and ASIO for dashboard server functionality.
- Enhanced existing database classes to support new dashboard features, including character, play key, and property management.
- Added new methods for retrieving and managing play keys, properties, and pet names.
- Updated TestSQLDatabase to include stubs for new dashboard-related methods.
- Modified shared and dashboard configuration files for new settings.
This commit is contained in:
Aaron Kimbrell
2026-04-22 11:01:41 -05:00
parent d532a9b063
commit e3467465b4
92 changed files with 9133 additions and 77 deletions

View File

@@ -25,6 +25,8 @@
#include "IAccountsRewardCodes.h"
#include "IBehaviors.h"
#include "IUgcModularBuild.h"
#include "IDashboardAuditLog.h"
#include "IDashboardConfig.h"
#ifdef _DEBUG
# define DLU_SQL_TRY_CATCH_RETHROW(x) do { try { x; } catch (std::exception& ex) { LOG("SQL Error: %s", ex.what()); throw; } } while(0)
@@ -38,7 +40,8 @@ class GameDatabase :
public IPropertyContents, public IProperty, public IPetNames, public ICharXml,
public IMigrationHistory, public IUgc, public IFriends, public ICharInfo,
public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList,
public IBehaviors, public IUgcModularBuild {
public IBehaviors, public IUgcModularBuild,
public IDashboardAuditLog, public IDashboardConfig {
public:
virtual ~GameDatabase() = default;
// TODO: These should be made private.

View File

@@ -5,6 +5,7 @@
#include <optional>
#include <string>
#include <string_view>
#include <vector>
enum class eGameMasterLevel : uint8_t;
@@ -38,7 +39,62 @@ public:
// Update the GameMaster level of an account.
virtual void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) = 0;
// Set the play_key_id for an account (used during registration)
virtual void UpdateAccountPlayKey(const uint32_t accountId, const uint32_t playKeyId) = 0;
// Get counts for dashboard/stats
virtual uint32_t GetBannedAccountCount() = 0;
virtual uint32_t GetLockedAccountCount() = 0;
virtual uint32_t GetAccountCount() = 0;
struct ListInfo {
uint32_t id{};
std::string name;
eGameMasterLevel gm_level{};
bool banned{};
bool locked{};
uint64_t mute_expire{};
uint32_t play_key_id{};
};
struct DetailedInfo {
uint32_t id{};
std::string name;
std::string email;
eGameMasterLevel gm_level{};
bool banned{};
bool locked{};
uint64_t mute_expire{};
uint32_t play_key_id{};
uint64_t created_at{};
};
struct SessionInfo {
uint64_t sessionId{};
std::string ipAddress;
uint64_t loginTime{};
uint64_t logoutTime{};
bool active{};
};
// Return all accounts for dashboard listing
virtual std::vector<ListInfo> GetAllAccounts() = 0;
// Update an account's locked status
virtual void UpdateAccountLock(const uint32_t accountId, const bool locked) = 0;
// Get detailed account info by ID (for dashboard viewing)
virtual std::optional<DetailedInfo> GetAccountById(const uint32_t accountId) = 0;
// Update account email (for dashboard)
virtual void UpdateAccountEmail(const uint32_t accountId, const std::string_view email) = 0;
// Delete account and all associated data
virtual void DeleteAccount(const uint32_t accountId) = 0;
// Get account session history
virtual std::vector<SessionInfo> GetAccountSessions(const uint32_t accountId, uint32_t limit = 50) = 0;
};
#endif //!__IACCOUNTS__H__

View File

@@ -15,6 +15,27 @@ class IActivityLog {
public:
// Update the activity log for the given account.
virtual void UpdateActivityLog(const LWOOBJID characterId, const eActivityType activityType, const LWOMAPID mapId) = 0;
struct Entry {
LWOOBJID characterId{};
eActivityType activity{};
uint32_t timestamp{};
LWOMAPID mapId{};
};
// Retrieve recent activity entries ordered by time desc.
virtual std::vector<Entry> GetRecentActivity(const uint32_t limit) = 0;
// Get total count of activity log entries
virtual uint32_t GetActivityLogCount() = 0;
// Get paginated activity log entries with ordering
virtual std::vector<Entry> GetActivityLogPaginated(
uint32_t offset,
uint32_t limit,
const std::string& orderColumn = "time",
const std::string& orderDir = "DESC"
) = 0;
};
#endif //!__IACTIVITYLOG__H__

View File

@@ -2,7 +2,10 @@
#define __IBUGREPORTS__H__
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
#include <optional>
class IBugReports {
public:
@@ -14,7 +17,29 @@ public:
LWOOBJID characterId{};
};
struct DetailedInfo {
uint64_t id{};
std::string body;
std::string clientVersion;
std::string otherPlayer;
std::string selection;
LWOOBJID characterId{};
uint64_t submitted{};
uint64_t resolved_time{};
uint32_t resolved_by_id{};
std::string resolution;
};
// Add a new bug report to the database.
virtual void InsertNewBugReport(const Info& info) = 0;
// Dashboard methods
virtual std::vector<DetailedInfo> GetAllBugReports() = 0;
virtual std::vector<DetailedInfo> GetUnresolvedBugReports() = 0;
virtual std::vector<DetailedInfo> GetResolvedBugReports() = 0;
virtual std::optional<DetailedInfo> GetBugReportById(const uint64_t reportId) = 0;
virtual void ResolveBugReport(const uint64_t reportId, const uint32_t resolvedById, const std::string_view resolution) = 0;
virtual uint32_t GetBugReportCount() = 0;
virtual uint32_t GetUnresolvedBugReportCount() = 0;
};
#endif //!__IBUGREPORTS__H__

View File

@@ -9,6 +9,9 @@
#include "ePermissionMap.h"
// Forward declare eActivityType for Activity struct
enum class eActivityType : uint32_t;
class ICharInfo {
public:
struct Info {
@@ -19,6 +22,35 @@ public:
bool needsRename{};
LWOCLONEID cloneId{};
ePermissionMap permissionMap{};
// Extended fields for dashboard
uint32_t level{};
uint64_t uscore{};
uint32_t zoneId{};
uint64_t lastLogin{};
uint64_t createdOn{};
};
struct Stats {
uint64_t totalCurrencyCollected{};
uint64_t totalBricksCollected{};
uint64_t totalSmashables{};
uint64_t totalQuickbuildsCompleted{};
uint64_t totalEnemiesSmashed{};
uint64_t totalRocketsUsed{};
uint64_t totalMissionsCompleted{};
uint64_t totalPetsTamed{};
};
struct InventoryItem {
LWOOBJID itemId{};
uint32_t count{};
int32_t slot{};
};
struct Activity {
uint64_t timestamp{};
eActivityType activity{};
uint32_t mapId{};
};
// Get the approved names of all characters.
@@ -46,6 +78,41 @@ public:
virtual void UpdateLastLoggedInCharacter(const LWOOBJID characterId) = 0;
virtual bool IsNameInUse(const std::string_view name) = 0;
// Get total count of characters
virtual uint32_t GetCharacterCount() = 0;
// Get paginated list of all characters
virtual std::vector<Info> GetAllCharactersPaginated(
uint32_t offset,
uint32_t limit,
const std::string& orderColumn = "id",
const std::string& orderDir = "DESC"
) = 0;
// Get characters with pending names (for moderation)
virtual std::vector<Info> GetCharactersWithPendingNames() = 0;
// Update character permission map (for restrictions)
virtual void UpdateCharacterPermissions(const LWOOBJID characterId, ePermissionMap permissions) = 0;
// Set needs rename flag
virtual void SetCharacterNeedsRename(const LWOOBJID characterId, bool needsRename) = 0;
// Get character statistics
virtual std::optional<Stats> GetCharacterStats(const LWOOBJID characterId) = 0;
// Get character inventory
virtual std::vector<InventoryItem> GetCharacterInventory(const LWOOBJID characterId) = 0;
// Get character activity history
virtual std::vector<Activity> GetCharacterActivity(const LWOOBJID characterId, uint32_t limit = 50) = 0;
// Rescue character to a safe zone
virtual void RescueCharacter(const LWOOBJID characterId, uint32_t zoneId) = 0;
// Delete character and all associated data
virtual void DeleteCharacter(const LWOOBJID characterId) = 0;
};
#endif //!__ICHARINFO__H__

View File

@@ -2,13 +2,24 @@
#define __ICOMMANDLOG__H__
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
class ICommandLog {
public:
public:
struct Entry {
uint64_t timestamp{};
LWOOBJID characterId{};
std::string command;
std::string arguments;
};
// Insert a new slash command log entry.
virtual void InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) = 0;
// Get recent command log entries
virtual std::vector<Entry> GetCommandLogs(uint32_t limit = 100) = 0;
};
#endif //!__ICOMMANDLOG__H__

View File

@@ -0,0 +1,58 @@
#ifndef __IDASHBOARDAUDITLOG__H__
#define __IDASHBOARDAUDITLOG__H__
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
/**
* Interface for Dashboard audit log table.
* Records all API requests, security events, and administrative actions.
*/
class IDashboardAuditLog {
public:
struct AuditLogEntry {
uint64_t id;
uint64_t timestamp;
std::string ip_address;
std::string endpoint;
std::string method;
std::string user_agent;
int32_t response_code;
};
struct AdminActionLog {
uint64_t timestamp;
uint32_t adminAccountId;
std::string action;
std::string targetType;
uint64_t targetId;
std::string details;
};
// Insert a new audit log entry for API requests
virtual void InsertAuditLog(const std::string_view ip, const std::string_view endpoint,
const std::string_view method, const std::string_view user_agent,
int32_t response_code) = 0;
// Insert a new admin action log entry
virtual void InsertAdminActionLog(uint32_t adminAccountId, const std::string_view action,
const std::string_view targetType, uint64_t targetId,
const std::string_view details) = 0;
// Get recent audit log entries (limit = number of entries)
virtual std::vector<AuditLogEntry> GetRecentAuditLogs(uint32_t limit = 100) = 0;
// Get recent admin action logs
virtual std::vector<AdminActionLog> GetAuditLogs(uint32_t limit = 100) = 0;
// Get audit logs for a specific IP address
virtual std::vector<AuditLogEntry> GetAuditLogsByIP(const std::string_view ip, uint32_t limit = 100) = 0;
// Clear old audit logs (older than days_to_keep)
virtual void CleanupOldAuditLogs(uint32_t days_to_keep = 30) = 0;
};
#endif //!__IDASHBOARDAUDITLOG__H__

View File

@@ -0,0 +1,27 @@
#ifndef __IDASHBOARDCONFIG__H__
#define __IDASHBOARDCONFIG__H__
#include <optional>
#include <string>
#include <string_view>
/**
* Interface for Dashboard configuration table.
* Stores key-value configuration settings for the Dashboard server.
*/
class IDashboardConfig {
public:
struct DashboardConfig {
std::string key;
std::string value;
};
// Get a configuration value
virtual std::optional<std::string> GetDashboardConfig(const std::string_view key) = 0;
// Set a configuration value
virtual void SetDashboardConfig(const std::string_view key, const std::string_view value) = 0;
};
#endif //!__IDASHBOARDCONFIG__H__

View File

@@ -3,6 +3,8 @@
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
class IPetNames {
public:
@@ -11,11 +13,24 @@ public:
int32_t approvalStatus{};
};
struct DetailedInfo {
LWOOBJID id{};
std::string petName;
int32_t approvalStatus{};
LWOOBJID ownerId{};
};
// Set the pet name moderation status for the given pet id.
virtual void SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) = 0;
// Get pet info for the given pet id.
virtual std::optional<IPetNames::Info> GetPetNameInfo(const LWOOBJID& petId) = 0;
// Dashboard methods
virtual std::vector<DetailedInfo> GetAllPetNames() = 0;
virtual std::vector<DetailedInfo> GetPetNamesByStatus(int32_t status) = 0;
virtual void SetPetApprovalStatus(const LWOOBJID& petId, int32_t status) = 0;
virtual uint32_t GetPendingPetNamesCount() = 0;
};
#endif //!__IPETNAMES__H__

View File

@@ -3,13 +3,38 @@
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
class IPlayKeys {
public:
struct Info {
uint32_t id{};
std::string key_string;
uint32_t key_uses{};
uint32_t times_used{};
bool active{};
std::string notes;
uint64_t created_at{};
};
// Get the playkey id for the given playkey.
// Optional of bool may seem pointless, however the optional indicates if the playkey exists
// and the bool indicates if the playkey is active.
virtual std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) = 0;
// Dashboard methods
virtual std::vector<Info> GetAllPlayKeys() = 0;
virtual std::optional<Info> GetPlayKeyById(const uint32_t playkeyId) = 0;
// Find a playkey by its string value (e.g. "ABCD-EFGH-...."). Returns Info if found.
virtual std::optional<Info> GetPlayKeyByString(const std::string_view key_string) = 0;
// Consume one usage of the given playkey id. Returns true if consumed, false if no uses left or not active.
virtual bool ConsumePlayKeyUsage(const uint32_t playkeyId) = 0;
virtual void CreatePlayKey(const std::string_view key_string, uint32_t uses, const std::string_view notes) = 0;
virtual void UpdatePlayKey(const uint32_t playkeyId, uint32_t uses, bool active, const std::string_view notes) = 0;
virtual void DeletePlayKey(const uint32_t playkeyId) = 0;
virtual uint32_t GetPlayKeyCount() = 0;
};
#endif //!__IPLAYKEYS__H__

View File

@@ -61,8 +61,14 @@ public:
// Update the property performance cost for the given property id.
virtual void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) = 0;
// Insert a new property into the database.
virtual void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) = 0;
// Dashboard methods
virtual std::vector<Info> GetAllProperties() = 0;
virtual std::vector<Info> GetPropertiesByApprovalStatus(uint32_t approved) = 0;
virtual uint32_t GetPropertyCount() = 0;
virtual uint32_t GetUnapprovedPropertyCount() = 0;
};
#endif //!__IPROPERTY__H__

View File

@@ -47,6 +47,9 @@ public:
void AddFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) override;
void RemoveFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) override;
void UpdateActivityLog(const LWOOBJID characterId, const eActivityType activityType, const LWOMAPID mapId) override;
std::vector<IActivityLog::Entry> GetRecentActivity(const uint32_t limit) override;
uint32_t GetActivityLogCount() override;
std::vector<IActivityLog::Entry> GetActivityLogPaginated(uint32_t offset, uint32_t limit, const std::string& orderColumn, const std::string& orderDir) override;
void DeleteUgcModelData(const LWOOBJID& modelId) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
std::vector<IUgc::Model> GetAllUgcModels() override;
@@ -58,6 +61,14 @@ public:
std::string GetCharacterXml(const LWOOBJID accountId) override;
void UpdateCharacterXml(const LWOOBJID characterId, const std::string_view lxfml) override;
std::optional<IAccounts::Info> GetAccountInfo(const std::string_view username) override;
// Account dashboard details
std::optional<IAccounts::DetailedInfo> GetAccountById(const uint32_t accountId) override;
void UpdateAccountEmail(const uint32_t accountId, const std::string_view email) override;
void DeleteAccount(const uint32_t accountId) override;
std::vector<IAccounts::ListInfo> GetAllAccounts() override;
void UpdateAccountLock(const uint32_t accountId, const bool locked) override;
std::vector<IAccounts::SessionInfo> GetAccountSessions(const uint32_t accountId, uint32_t limit = 50) override;
void InsertNewCharacter(const ICharInfo::Info info) override;
void InsertCharacterXml(const LWOOBJID accountId, const std::string_view lxfml) override;
std::vector<LWOOBJID> GetAccountCharacterIds(LWOOBJID accountId) override;
@@ -67,6 +78,12 @@ public:
void UpdateLastLoggedInCharacter(const LWOOBJID characterId) override;
void SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) override;
std::optional<IPetNames::Info> GetPetNameInfo(const LWOOBJID& petId) override;
// Pet name moderation
std::vector<IPetNames::DetailedInfo> GetAllPetNames() override;
std::vector<IPetNames::DetailedInfo> GetPetNamesByStatus(int32_t status) override;
void SetPetApprovalStatus(const LWOOBJID& petId, int32_t status) override;
uint32_t GetPendingPetNamesCount() override;
std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override;
void UpdatePropertyModerationInfo(const IProperty::Info& info) override;
void UpdatePropertyDetails(const IProperty::Info& info) override;
@@ -79,6 +96,15 @@ public:
void RemoveModel(const LWOOBJID& modelId) override;
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override;
// Bug reports (dashboard)
std::vector<IBugReports::DetailedInfo> GetAllBugReports() override;
std::vector<IBugReports::DetailedInfo> GetUnresolvedBugReports() override;
std::vector<IBugReports::DetailedInfo> GetResolvedBugReports() override;
std::optional<IBugReports::DetailedInfo> GetBugReportById(const uint64_t reportId) override;
void ResolveBugReport(const uint64_t reportId, const uint32_t resolvedById, const std::string_view resolution) override;
uint32_t GetBugReportCount() override;
uint32_t GetUnresolvedBugReportCount() override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const MailInfo& mail) override;
void InsertNewUgcModel(
@@ -93,6 +119,7 @@ public:
void DeleteMail(const uint64_t mailId) override;
void ClaimMailItem(const uint64_t mailId) override;
void InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) override;
std::vector<ICommandLog::Entry> GetCommandLogs(uint32_t limit = 100) override;
void UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) override;
void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override;
@@ -103,6 +130,16 @@ public:
void InsertDefaultPersistentId() override;
std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override;
std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override;
// Play keys management
std::vector<IPlayKeys::Info> GetAllPlayKeys() override;
std::optional<IPlayKeys::Info> GetPlayKeyById(const uint32_t playkeyId) override;
std::optional<IPlayKeys::Info> GetPlayKeyByString(const std::string_view key_string) override;
bool ConsumePlayKeyUsage(const uint32_t playkeyId) override;
void CreatePlayKey(const std::string_view key_string, uint32_t uses, const std::string_view notes) override;
void UpdatePlayKey(const uint32_t playkeyId, uint32_t uses, bool active, const std::string_view notes) override;
void DeletePlayKey(const uint32_t playkeyId) override;
uint32_t GetPlayKeyCount() override;
std::vector<IUgc::Model> GetUgcModels(const LWOOBJID& propertyId) override;
void AddIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) override;
void RemoveIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) override;
@@ -113,6 +150,7 @@ public:
std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const LWOOBJID characterId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
void UpdateAccountPlayKey(const uint32_t accountId, const uint32_t playKeyId) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override;
@@ -126,10 +164,46 @@ public:
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<LWOOBJID> characterId) override;
void DeleteUgcBuild(const LWOOBJID bigId) override;
uint32_t GetAccountCount() override;
uint32_t GetBannedAccountCount() override;
uint32_t GetLockedAccountCount() override;
bool IsNameInUse(const std::string_view name) override;
uint32_t GetCharacterCount() override;
std::vector<ICharInfo::Info> GetAllCharactersPaginated(uint32_t offset, uint32_t limit, const std::string& orderColumn, const std::string& orderDir) override;
std::vector<ICharInfo::Info> GetCharactersWithPendingNames() override;
// Character management additions
void UpdateCharacterPermissions(const LWOOBJID characterId, ePermissionMap permissions) override;
void SetCharacterNeedsRename(const LWOOBJID characterId, bool needsRename) override;
std::optional<ICharInfo::Stats> GetCharacterStats(const LWOOBJID characterId) override;
std::vector<ICharInfo::InventoryItem> GetCharacterInventory(const LWOOBJID characterId) override;
std::vector<ICharInfo::Activity> GetCharacterActivity(const LWOOBJID characterId, uint32_t limit = 50) override;
void RescueCharacter(const LWOOBJID characterId, uint32_t zoneId) override;
std::optional<IPropertyContents::Model> GetModel(const LWOOBJID modelID) override;
std::optional<IUgc::Model> GetUgcModel(const LWOOBJID ugcId) override;
std::optional<IProperty::Info> GetPropertyInfo(const LWOOBJID id) override;
// Property listing/approval (dashboard)
std::vector<IProperty::Info> GetAllProperties() override;
std::vector<IProperty::Info> GetPropertiesByApprovalStatus(uint32_t approved) override;
uint32_t GetPropertyCount() override;
uint32_t GetUnapprovedPropertyCount() override;
// Dashboard Audit Log
void InsertAuditLog(const std::string_view ip_address, const std::string_view endpoint,
const std::string_view method, const std::string_view user_agent, int32_t response_code) override;
std::vector<IDashboardAuditLog::AuditLogEntry> GetRecentAuditLogs(uint32_t limit) override;
std::vector<IDashboardAuditLog::AuditLogEntry> GetAuditLogsByIP(const std::string_view ip_address, uint32_t limit) override;
void CleanupOldAuditLogs(uint32_t days_to_keep) override;
void InsertAdminActionLog(uint32_t adminAccountId, const std::string_view action,
const std::string_view targetType, uint64_t targetId,
const std::string_view details) override;
std::vector<IDashboardAuditLog::AdminActionLog> GetAuditLogs(uint32_t limit = 100) override;
// Dashboard Config
std::optional<std::string> GetDashboardConfig(const std::string_view config_key) override;
void SetDashboardConfig(const std::string_view config_key, const std::string_view config_value) override;
sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
private:

View File

@@ -41,7 +41,85 @@ void MySQLDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGameMa
ExecuteUpdate("UPDATE accounts SET gm_level = ? WHERE id = ?;", static_cast<int32_t>(gmLevel), accountId);
}
void MySQLDatabase::UpdateAccountPlayKey(const uint32_t accountId, const uint32_t playKeyId) {
ExecuteUpdate("UPDATE accounts SET play_key_id = ? WHERE id = ?;", playKeyId, accountId);
}
uint32_t MySQLDatabase::GetAccountCount() {
auto res = ExecuteSelect("SELECT COUNT(*) as count FROM accounts;");
return res->next() ? res->getUInt("count") : 0;
}
uint32_t MySQLDatabase::GetBannedAccountCount() {
auto res = ExecuteSelect("SELECT COUNT(*) as count FROM accounts WHERE banned = 1;");
return res->next() ? res->getUInt("count") : 0;
}
uint32_t MySQLDatabase::GetLockedAccountCount() {
auto res = ExecuteSelect("SELECT COUNT(*) as count FROM accounts WHERE locked = 1;");
return res->next() ? res->getUInt("count") : 0;
}
std::vector<IAccounts::ListInfo> MySQLDatabase::GetAllAccounts() {
std::vector<IAccounts::ListInfo> out;
auto res = ExecuteSelect("SELECT id, name, gm_level, banned, locked, mute_expire, play_key_id FROM accounts ORDER BY id ASC;");
while (res->next()) {
IAccounts::ListInfo info;
info.id = res->getUInt("id");
info.name = res->getString("name").c_str();
info.gm_level = static_cast<eGameMasterLevel>(res->getInt("gm_level"));
info.banned = res->getBoolean("banned");
info.locked = res->getBoolean("locked");
info.mute_expire = res->getUInt64("mute_expire");
info.play_key_id = res->getUInt("play_key_id");
out.push_back(info);
}
return out;
}
void MySQLDatabase::UpdateAccountLock(const uint32_t accountId, const bool locked) {
ExecuteUpdate("UPDATE accounts SET locked = ? WHERE id = ?;", locked, accountId);
}
std::optional<IAccounts::DetailedInfo> MySQLDatabase::GetAccountById(const uint32_t accountId) {
auto result = ExecuteSelect(
"SELECT id, name, email, gm_level, banned, locked, mute_expire, play_key_id, created_at FROM accounts WHERE id = ? LIMIT 1;",
accountId
);
if (!result->next()) {
return std::nullopt;
}
IAccounts::DetailedInfo info;
info.id = result->getUInt("id");
info.name = result->getString("name").c_str();
info.email = result->getString("email").c_str();
info.gm_level = static_cast<eGameMasterLevel>(result->getInt("gm_level"));
info.banned = result->getBoolean("banned");
info.locked = result->getBoolean("locked");
info.mute_expire = result->getUInt64("mute_expire");
info.play_key_id = result->getUInt("play_key_id");
info.created_at = result->getUInt64("created_at");
return info;
}
void MySQLDatabase::UpdateAccountEmail(const uint32_t accountId, const std::string_view email) {
ExecuteUpdate("UPDATE accounts SET email = ? WHERE id = ?;", email, accountId);
}
void MySQLDatabase::DeleteAccount(const uint32_t accountId) {
// Delete all associated data first
// Characters and their data will be handled by CASCADE or manual deletion
ExecuteDelete("DELETE FROM char_info WHERE account_id = ?;", accountId);
ExecuteDelete("DELETE FROM accounts WHERE id = ?;", accountId);
}
std::vector<IAccounts::SessionInfo> MySQLDatabase::GetAccountSessions(const uint32_t accountId, uint32_t limit) {
// account_sessions table doesn't exist in the current schema
// Session tracking would need to be implemented separately
return {};
}

View File

@@ -4,3 +4,60 @@ void MySQLDatabase::UpdateActivityLog(const LWOOBJID characterId, const eActivit
ExecuteInsert("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);",
characterId, static_cast<uint32_t>(activityType), static_cast<uint32_t>(time(NULL)), mapId);
}
std::vector<IActivityLog::Entry> MySQLDatabase::GetRecentActivity(const uint32_t limit) {
std::vector<IActivityLog::Entry> out;
auto res = ExecuteSelect("SELECT character_id, activity, time, map_id FROM activity_log ORDER BY time DESC LIMIT ?;", limit);
while (res->next()) {
IActivityLog::Entry e;
e.characterId = static_cast<LWOOBJID>(res->getUInt64("character_id"));
e.activity = static_cast<eActivityType>(res->getInt("activity"));
e.timestamp = static_cast<uint32_t>(res->getUInt("time"));
e.mapId = static_cast<LWOMAPID>(res->getUInt("map_id"));
out.push_back(e);
}
return out;
}
uint32_t MySQLDatabase::GetActivityLogCount() {
auto res = ExecuteSelect("SELECT COUNT(*) as count FROM activity_log;");
return res->next() ? res->getUInt("count") : 0;
}
std::vector<IActivityLog::Entry> MySQLDatabase::GetActivityLogPaginated(
uint32_t offset,
uint32_t limit,
const std::string& orderColumn,
const std::string& orderDir
) {
std::vector<IActivityLog::Entry> out;
// Validate orderColumn to prevent SQL injection
std::string validColumn = "time";
if (orderColumn == "character_id" || orderColumn == "activity" || orderColumn == "map_id" || orderColumn == "time") {
validColumn = orderColumn;
}
// Validate orderDir
std::string validDir = (orderDir == "ASC" || orderDir == "asc") ? "ASC" : "DESC";
// Build query - can't use prepared statement for ORDER BY clause
std::string query = "SELECT character_id, activity, time, map_id FROM activity_log ORDER BY " +
validColumn + " " + validDir + " LIMIT ? OFFSET ?;";
auto res = ExecuteSelect(query, limit, offset);
while (res->next()) {
IActivityLog::Entry e;
e.characterId = static_cast<LWOOBJID>(res->getUInt64("character_id"));
e.activity = static_cast<eActivityType>(res->getInt("activity"));
e.timestamp = static_cast<uint32_t>(res->getUInt("time"));
e.mapId = static_cast<LWOMAPID>(res->getUInt("map_id"));
out.push_back(e);
}
return out;
}

View File

@@ -4,3 +4,125 @@ void MySQLDatabase::InsertNewBugReport(const IBugReports::Info& info) {
ExecuteInsert("INSERT INTO `bug_reports`(body, client_version, other_player_id, selection, reporter_id) VALUES (?, ?, ?, ?, ?)",
info.body, info.clientVersion, info.otherPlayer, info.selection, info.characterId);
}
std::vector<IBugReports::DetailedInfo> MySQLDatabase::GetAllBugReports() {
std::vector<IBugReports::DetailedInfo> out;
auto res = ExecuteSelect(
"SELECT id, body, client_version, other_player_id, selection, reporter_id, "
"UNIX_TIMESTAMP(submitted) as submitted, UNIX_TIMESTAMP(resolved_time) as resolved_time, "
"resolved_by_id, resolution FROM bug_reports ORDER BY id DESC;"
);
while (res->next()) {
IBugReports::DetailedInfo info;
info.id = res->getUInt64("id");
info.body = res->getString("body").c_str();
info.clientVersion = res->getString("client_version").c_str();
info.otherPlayer = res->getString("other_player_id").c_str();
info.selection = res->getString("selection").c_str();
info.characterId = res->getUInt64("reporter_id");
info.submitted = res->getUInt64("submitted");
info.resolved_time = res->getUInt64("resolved_time");
info.resolved_by_id = res->getUInt("resolved_by_id");
info.resolution = res->getString("resolution").c_str();
out.push_back(info);
}
return out;
}
std::vector<IBugReports::DetailedInfo> MySQLDatabase::GetUnresolvedBugReports() {
std::vector<IBugReports::DetailedInfo> out;
auto res = ExecuteSelect(
"SELECT id, body, client_version, other_player_id, selection, reporter_id, "
"UNIX_TIMESTAMP(submitted) as submitted, UNIX_TIMESTAMP(resolved_time) as resolved_time, "
"resolved_by_id, resolution FROM bug_reports WHERE resolved_time IS NULL ORDER BY id DESC;"
);
while (res->next()) {
IBugReports::DetailedInfo info;
info.id = res->getUInt64("id");
info.body = res->getString("body").c_str();
info.clientVersion = res->getString("client_version").c_str();
info.otherPlayer = res->getString("other_player_id").c_str();
info.selection = res->getString("selection").c_str();
info.characterId = res->getUInt64("reporter_id");
info.submitted = res->getUInt64("submitted");
info.resolved_time = 0;
info.resolved_by_id = 0;
info.resolution = "";
out.push_back(info);
}
return out;
}
std::vector<IBugReports::DetailedInfo> MySQLDatabase::GetResolvedBugReports() {
std::vector<IBugReports::DetailedInfo> out;
auto res = ExecuteSelect(
"SELECT id, body, client_version, other_player_id, selection, reporter_id, "
"UNIX_TIMESTAMP(submitted) as submitted, UNIX_TIMESTAMP(resolved_time) as resolved_time, "
"resolved_by_id, resolution FROM bug_reports WHERE resolved_time IS NOT NULL ORDER BY id DESC;"
);
while (res->next()) {
IBugReports::DetailedInfo info;
info.id = res->getUInt64("id");
info.body = res->getString("body").c_str();
info.clientVersion = res->getString("client_version").c_str();
info.otherPlayer = res->getString("other_player_id").c_str();
info.selection = res->getString("selection").c_str();
info.characterId = res->getUInt64("reporter_id");
info.submitted = res->getUInt64("submitted");
info.resolved_time = res->getUInt64("resolved_time");
info.resolved_by_id = res->getUInt("resolved_by_id");
info.resolution = res->getString("resolution").c_str();
out.push_back(info);
}
return out;
}
std::optional<IBugReports::DetailedInfo> MySQLDatabase::GetBugReportById(const uint64_t reportId) {
auto result = ExecuteSelect(
"SELECT id, body, client_version, other_player_id, selection, reporter_id, "
"UNIX_TIMESTAMP(submitted) as submitted, UNIX_TIMESTAMP(resolved_time) as resolved_time, "
"resolved_by_id, resolution FROM bug_reports WHERE id = ? LIMIT 1;",
reportId
);
if (!result->next()) {
return std::nullopt;
}
IBugReports::DetailedInfo info;
info.id = result->getUInt64("id");
info.body = result->getString("body").c_str();
info.clientVersion = result->getString("client_version").c_str();
info.otherPlayer = result->getString("other_player_id").c_str();
info.selection = result->getString("selection").c_str();
info.characterId = result->getUInt64("reporter_id");
info.submitted = result->getUInt64("submitted");
info.resolved_time = result->isNull("resolved_time") ? 0 : result->getUInt64("resolved_time");
info.resolved_by_id = result->isNull("resolved_by_id") ? 0 : result->getUInt("resolved_by_id");
info.resolution = result->getString("resolution").c_str();
return info;
}
void MySQLDatabase::ResolveBugReport(const uint64_t reportId, const uint32_t resolvedById, const std::string_view resolution) {
ExecuteUpdate(
"UPDATE bug_reports SET resolved_time = NOW(), resolved_by_id = ?, resolution = ? WHERE id = ?;",
resolvedById, resolution, reportId
);
}
uint32_t MySQLDatabase::GetBugReportCount() {
auto res = ExecuteSelect("SELECT COUNT(*) as count FROM bug_reports;");
return res->next() ? res->getUInt("count") : 0;
}
uint32_t MySQLDatabase::GetUnresolvedBugReportCount() {
auto res = ExecuteSelect("SELECT COUNT(*) as count FROM bug_reports WHERE resolved_time IS NULL;");
return res->next() ? res->getUInt("count") : 0;
}

View File

@@ -21,6 +21,8 @@ set(DDATABASES_DATABASES_MYSQL_TABLES_SOURCES
"Servers.cpp"
"Ugc.cpp"
"UgcModularBuild.cpp"
"DashboardAuditLog.cpp"
"DashboardConfig.cpp"
PARENT_SCOPE
)

View File

@@ -82,3 +82,112 @@ bool MySQLDatabase::IsNameInUse(const std::string_view name) {
return result->next();
}
uint32_t MySQLDatabase::GetCharacterCount() {
auto res = ExecuteSelect("SELECT COUNT(*) as count FROM charinfo;");
return res->next() ? res->getUInt("count") : 0;
}
std::vector<ICharInfo::Info> MySQLDatabase::GetAllCharactersPaginated(
uint32_t offset,
uint32_t limit,
const std::string& orderColumn,
const std::string& orderDir
) {
std::vector<ICharInfo::Info> out;
// Validate orderColumn to prevent SQL injection
std::string validColumn = "id";
if (orderColumn == "name" || orderColumn == "account_id" || orderColumn == "id" || orderColumn == "last_login") {
validColumn = orderColumn;
}
// Validate orderDir
std::string validDir = (orderDir == "ASC" || orderDir == "asc") ? "ASC" : "DESC";
// Build query
std::string query = "SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo ORDER BY " +
validColumn + " " + validDir + " LIMIT ? OFFSET ?;";
auto res = ExecuteSelect(query, limit, offset);
while (res->next()) {
ICharInfo::Info info;
info.id = res->getInt64("id");
info.name = res->getString("name").c_str();
info.pendingName = res->getString("pending_name").c_str();
info.needsRename = res->getBoolean("needs_rename");
info.cloneId = res->getUInt64("prop_clone_id");
info.accountId = res->getUInt("account_id");
info.permissionMap = static_cast<ePermissionMap>(res->getUInt("permission_map"));
out.push_back(info);
}
return out;
}
std::vector<ICharInfo::Info> MySQLDatabase::GetCharactersWithPendingNames() {
std::vector<ICharInfo::Info> out;
auto res = ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE pending_name != '' ORDER BY id ASC;");
while (res->next()) {
ICharInfo::Info info;
info.id = res->getInt64("id");
info.name = res->getString("name").c_str();
info.pendingName = res->getString("pending_name").c_str();
info.needsRename = res->getBoolean("needs_rename");
info.cloneId = res->getUInt64("prop_clone_id");
info.accountId = res->getUInt("account_id");
info.permissionMap = static_cast<ePermissionMap>(res->getUInt("permission_map"));
out.push_back(info);
}
return out;
}
void MySQLDatabase::UpdateCharacterPermissions(const LWOOBJID characterId, ePermissionMap permissions) {
ExecuteUpdate("UPDATE charinfo SET permission_map = ? WHERE id = ? LIMIT 1;", static_cast<uint64_t>(permissions), characterId);
}
void MySQLDatabase::SetCharacterNeedsRename(const LWOOBJID characterId, bool needsRename) {
ExecuteUpdate("UPDATE charinfo SET needs_rename = ? WHERE id = ? LIMIT 1;", needsRename, characterId);
}
std::optional<ICharInfo::Stats> MySQLDatabase::GetCharacterStats(const LWOOBJID characterId) {
// char_stats table doesn't exist in the current schema
// Stats would need to be parsed from charxml or a new table created
return std::nullopt;
}
std::vector<ICharInfo::InventoryItem> MySQLDatabase::GetCharacterInventory(const LWOOBJID characterId) {
// Inventory data is stored in charxml, not a separate table
// Would need to parse the XML to extract inventory items
// Returning empty for now - implement XML parsing if needed
return {};
}
std::vector<ICharInfo::Activity> MySQLDatabase::GetCharacterActivity(const LWOOBJID characterId, uint32_t limit) {
auto res = ExecuteSelect(
"SELECT time, activity, map_id FROM activity_log WHERE character_id = ? ORDER BY time DESC LIMIT ?;",
characterId, limit
);
std::vector<ICharInfo::Activity> activities;
while (res->next()) {
ICharInfo::Activity activity;
activity.timestamp = res->getUInt64("time");
activity.activity = static_cast<eActivityType>(res->getUInt("activity"));
activity.mapId = res->getUInt("map_id");
activities.push_back(activity);
}
return activities;
}
void MySQLDatabase::RescueCharacter(const LWOOBJID characterId, uint32_t zoneId) {
// The rescue is now handled by the chat server API which kicks the player
// and modifies the XML after the player data is saved
// This database method is intentionally a no-op as the actual work
// is done via DashboardHelpers::RescueCharacter()
}

View File

@@ -3,3 +3,19 @@
void MySQLDatabase::InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) {
ExecuteInsert("INSERT INTO command_log (character_id, command) VALUES (?, ?);", characterId, command);
}
std::vector<ICommandLog::Entry> MySQLDatabase::GetCommandLogs(uint32_t limit) {
std::vector<ICommandLog::Entry> logs;
auto res = ExecuteSelect("SELECT id, character_id, command FROM command_log ORDER BY id DESC LIMIT ?;", limit);
while (res->next()) {
ICommandLog::Entry entry;
entry.timestamp = 0; // Timestamp column doesn't exist in command_log table
entry.characterId = res->getInt64("character_id");
entry.command = res->getString("command").c_str();
entry.arguments = ""; // Arguments not currently stored in DB
logs.push_back(entry);
}
return logs;
}

View File

@@ -0,0 +1,66 @@
#include "MySQLDatabase.h"
#include <ctime>
void MySQLDatabase::InsertAuditLog(const std::string_view ip, const std::string_view endpoint,
const std::string_view method, const std::string_view user_agent, int32_t response_code) {
uint64_t now = static_cast<uint64_t>(std::time(nullptr));
ExecuteInsert("INSERT INTO dashboard_audit_log (timestamp, ip_address, endpoint, method, user_agent, response_code) VALUES (?, ?, ?, ?, ?, ?);",
now, ip, endpoint, method, user_agent, response_code);
}
std::vector<IDashboardAuditLog::AuditLogEntry> MySQLDatabase::GetRecentAuditLogs(uint32_t limit) {
std::vector<IDashboardAuditLog::AuditLogEntry> logs;
auto res = ExecuteSelect("SELECT id, timestamp, ip_address, endpoint, method, user_agent, response_code FROM dashboard_audit_log ORDER BY timestamp DESC LIMIT ?;", limit);
while (res->next()) {
IDashboardAuditLog::AuditLogEntry entry;
entry.id = res->getUInt64("id");
entry.timestamp = res->getUInt64("timestamp");
entry.ip_address = res->getString("ip_address").c_str();
entry.endpoint = res->getString("endpoint").c_str();
entry.method = res->getString("method").c_str();
entry.user_agent = res->getString("user_agent").c_str();
entry.response_code = res->getInt("response_code");
logs.push_back(entry);
}
return logs;
}
std::vector<IDashboardAuditLog::AuditLogEntry> MySQLDatabase::GetAuditLogsByIP(const std::string_view ip, uint32_t limit) {
std::vector<IDashboardAuditLog::AuditLogEntry> logs;
auto res = ExecuteSelect("SELECT id, timestamp, ip_address, endpoint, method, user_agent, response_code FROM dashboard_audit_log WHERE ip_address = ? ORDER BY timestamp DESC LIMIT ?;", ip, limit);
while (res->next()) {
IDashboardAuditLog::AuditLogEntry entry;
entry.id = res->getUInt64("id");
entry.timestamp = res->getUInt64("timestamp");
entry.ip_address = res->getString("ip_address").c_str();
entry.endpoint = res->getString("endpoint").c_str();
entry.method = res->getString("method").c_str();
entry.user_agent = res->getString("user_agent").c_str();
entry.response_code = res->getInt("response_code");
logs.push_back(entry);
}
return logs;
}
void MySQLDatabase::CleanupOldAuditLogs(uint32_t days_to_keep) {
uint64_t cutoff_time = static_cast<uint64_t>(std::time(nullptr)) - (static_cast<uint64_t>(days_to_keep) * 86400ULL);
ExecuteDelete("DELETE FROM dashboard_audit_log WHERE timestamp < ?;", cutoff_time);
}
void MySQLDatabase::InsertAdminActionLog(uint32_t adminAccountId, const std::string_view action,
const std::string_view targetType, uint64_t targetId,
const std::string_view details) {
// dashboard_admin_action_log table doesn't exist
// Admin actions could be logged to dashboard_audit_log or a new table needs to be created
// For now, this is a no-op
}
std::vector<IDashboardAuditLog::AdminActionLog> MySQLDatabase::GetAuditLogs(uint32_t limit) {
// dashboard_admin_action_log table doesn't exist
// Would need to create a new table or use dashboard_audit_log with additional columns
return {};
}

View File

@@ -0,0 +1,14 @@
#include "MySQLDatabase.h"
std::optional<std::string> MySQLDatabase::GetDashboardConfig(const std::string_view key) {
auto res = ExecuteSelect("SELECT config_value FROM dashboard_config WHERE config_key = ? LIMIT 1;", key);
if (res->next()) {
return res->getString("config_value").c_str();
}
return std::nullopt;
}
void MySQLDatabase::SetDashboardConfig(const std::string_view key, const std::string_view value) {
// Use INSERT ... ON DUPLICATE KEY UPDATE for MySQL
ExecuteInsert("INSERT INTO dashboard_config (config_key, config_value, updated_at) VALUES (?, ?, UNIX_TIMESTAMP()) ON DUPLICATE KEY UPDATE config_value = VALUES(config_value), updated_at = VALUES(updated_at);", key, value);
}

View File

@@ -24,3 +24,44 @@ std::optional<IPetNames::Info> MySQLDatabase::GetPetNameInfo(const LWOOBJID& pet
return toReturn;
}
std::vector<IPetNames::DetailedInfo> MySQLDatabase::GetAllPetNames() {
std::vector<IPetNames::DetailedInfo> out;
auto res = ExecuteSelect("SELECT id, pet_name, approved, owner_id FROM pet_names ORDER BY id DESC;");
while (res->next()) {
IPetNames::DetailedInfo info;
info.id = res->getUInt64("id");
info.petName = res->getString("pet_name").c_str();
info.approvalStatus = res->getInt("approved");
info.ownerId = res->getUInt64("owner_id");
out.push_back(info);
}
return out;
}
std::vector<IPetNames::DetailedInfo> MySQLDatabase::GetPetNamesByStatus(int32_t status) {
std::vector<IPetNames::DetailedInfo> out;
auto res = ExecuteSelect("SELECT id, pet_name, approved, owner_id FROM pet_names WHERE approved = ? ORDER BY id DESC;", status);
while (res->next()) {
IPetNames::DetailedInfo info;
info.id = res->getUInt64("id");
info.petName = res->getString("pet_name").c_str();
info.approvalStatus = res->getInt("approved");
info.ownerId = res->getUInt64("owner_id");
out.push_back(info);
}
return out;
}
void MySQLDatabase::SetPetApprovalStatus(const LWOOBJID& petId, int32_t status) {
ExecuteUpdate("UPDATE pet_names SET approved = ? WHERE id = ?;", status, petId);
}
uint32_t MySQLDatabase::GetPendingPetNamesCount() {
auto res = ExecuteSelect("SELECT COUNT(*) as count FROM pet_names WHERE approved = 1;");
return res->next() ? res->getUInt("count") : 0;
}

View File

@@ -9,3 +9,117 @@ std::optional<bool> MySQLDatabase::IsPlaykeyActive(const int32_t playkeyId) {
return keyCheckRes->getBoolean("active");
}
std::vector<IPlayKeys::Info> MySQLDatabase::GetAllPlayKeys() {
std::vector<IPlayKeys::Info> out;
auto res = ExecuteSelect(
"SELECT id, key_string, key_uses, times_used, active, notes, UNIX_TIMESTAMP(created_at) as created_at "
"FROM play_keys ORDER BY id DESC;"
);
while (res->next()) {
IPlayKeys::Info info;
info.id = res->getUInt("id");
info.key_string = res->getString("key_string").c_str();
info.key_uses = res->getUInt("key_uses");
info.times_used = res->getUInt("times_used");
info.active = res->getBoolean("active");
info.notes = res->getString("notes").c_str();
info.created_at = res->getUInt64("created_at");
out.push_back(info);
}
return out;
}
std::optional<IPlayKeys::Info> MySQLDatabase::GetPlayKeyById(const uint32_t playkeyId) {
auto result = ExecuteSelect(
"SELECT id, key_string, key_uses, times_used, active, notes, UNIX_TIMESTAMP(created_at) as created_at "
"FROM play_keys WHERE id = ? LIMIT 1;",
playkeyId
);
if (!result->next()) {
return std::nullopt;
}
IPlayKeys::Info info;
info.id = result->getUInt("id");
info.key_string = result->getString("key_string").c_str();
info.key_uses = result->getUInt("key_uses");
info.times_used = result->getUInt("times_used");
info.active = result->getBoolean("active");
info.notes = result->getString("notes").c_str();
info.created_at = result->getUInt64("created_at");
return info;
}
std::optional<IPlayKeys::Info> MySQLDatabase::GetPlayKeyByString(const std::string_view key_string) {
auto result = ExecuteSelect(
"SELECT id, key_string, key_uses, times_used, active, notes, UNIX_TIMESTAMP(created_at) as created_at "
"FROM play_keys WHERE key_string = ? LIMIT 1;",
key_string
);
if (!result->next()) {
return std::nullopt;
}
IPlayKeys::Info info;
info.id = result->getUInt("id");
info.key_string = result->getString("key_string").c_str();
info.key_uses = result->getUInt("key_uses");
info.times_used = result->getUInt("times_used");
info.active = result->getBoolean("active");
info.notes = result->getString("notes").c_str();
info.created_at = result->getUInt64("created_at");
return info;
}
bool MySQLDatabase::ConsumePlayKeyUsage(const uint32_t playkeyId) {
// Check current state
auto res = ExecuteSelect("SELECT key_uses, times_used, active FROM play_keys WHERE id = ? LIMIT 1;", playkeyId);
if (!res->next()) return false;
int key_uses = res->getUInt("key_uses");
int times_used = res->getUInt("times_used");
bool active = res->getBoolean("active");
if (!active) return false;
if (times_used >= key_uses) return false;
// Increment times_used
ExecuteUpdate("UPDATE play_keys SET times_used = times_used + 1 WHERE id = ?;", playkeyId);
// Deactivate if used up
if (times_used + 1 >= key_uses) {
ExecuteUpdate("UPDATE play_keys SET active = 0 WHERE id = ?;", playkeyId);
}
return true;
}
void MySQLDatabase::CreatePlayKey(const std::string_view key_string, uint32_t uses, const std::string_view notes) {
ExecuteInsert(
"INSERT INTO play_keys (key_string, key_uses, times_used, active, notes) VALUES (?, ?, 0, 1, ?);",
key_string, uses, notes
);
}
void MySQLDatabase::UpdatePlayKey(const uint32_t playkeyId, uint32_t uses, bool active, const std::string_view notes) {
ExecuteUpdate(
"UPDATE play_keys SET key_uses = ?, active = ?, notes = ? WHERE id = ?;",
uses, active, notes, playkeyId
);
}
void MySQLDatabase::DeletePlayKey(const uint32_t playkeyId) {
ExecuteDelete("DELETE FROM play_keys WHERE id = ?;", playkeyId);
}
uint32_t MySQLDatabase::GetPlayKeyCount() {
auto res = ExecuteSelect("SELECT COUNT(*) as count FROM play_keys;");
return res->next() ? res->getUInt("count") : 0;
}

View File

@@ -198,3 +198,44 @@ std::optional<IProperty::Info> MySQLDatabase::GetPropertyInfo(const LWOOBJID id)
return ReadPropertyInfo(propertyEntry);
}
std::vector<IProperty::Info> MySQLDatabase::GetAllProperties() {
std::vector<IProperty::Info> out;
auto res = ExecuteSelect(
"SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, "
"last_updated, time_claimed, reputation, mod_approved, performance_cost "
"FROM properties ORDER BY id DESC;"
);
while (res->next()) {
out.push_back(ReadPropertyInfo(res));
}
return out;
}
std::vector<IProperty::Info> MySQLDatabase::GetPropertiesByApprovalStatus(uint32_t approved) {
std::vector<IProperty::Info> out;
auto res = ExecuteSelect(
"SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, "
"last_updated, time_claimed, reputation, mod_approved, performance_cost "
"FROM properties WHERE mod_approved = ? ORDER BY id DESC;",
approved
);
while (res->next()) {
out.push_back(ReadPropertyInfo(res));
}
return out;
}
uint32_t MySQLDatabase::GetPropertyCount() {
auto res = ExecuteSelect("SELECT COUNT(*) as count FROM properties;");
return res->next() ? res->getUInt("count") : 0;
}
uint32_t MySQLDatabase::GetUnapprovedPropertyCount() {
auto res = ExecuteSelect("SELECT COUNT(*) as count FROM properties WHERE mod_approved = 0;");
return res->next() ? res->getUInt("count") : 0;
}

View File

@@ -45,6 +45,9 @@ public:
void AddFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) override;
void RemoveFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) override;
void UpdateActivityLog(const LWOOBJID characterId, const eActivityType activityType, const LWOMAPID mapId) override;
std::vector<IActivityLog::Entry> GetRecentActivity(const uint32_t limit) override;
uint32_t GetActivityLogCount() override;
std::vector<IActivityLog::Entry> GetActivityLogPaginated(uint32_t offset, uint32_t limit, const std::string& orderColumn, const std::string& orderDir) override;
void DeleteUgcModelData(const LWOOBJID& modelId) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
std::vector<IUgc::Model> GetAllUgcModels() override;
@@ -56,6 +59,14 @@ public:
std::string GetCharacterXml(const LWOOBJID accountId) override;
void UpdateCharacterXml(const LWOOBJID characterId, const std::string_view lxfml) override;
std::optional<IAccounts::Info> GetAccountInfo(const std::string_view username) override;
// Account dashboard details
std::optional<IAccounts::DetailedInfo> GetAccountById(const uint32_t accountId) override;
void UpdateAccountEmail(const uint32_t accountId, const std::string_view email) override;
void DeleteAccount(const uint32_t accountId) override;
std::vector<IAccounts::ListInfo> GetAllAccounts() override;
void UpdateAccountLock(const uint32_t accountId, const bool locked) override;
std::vector<IAccounts::SessionInfo> GetAccountSessions(const uint32_t accountId, uint32_t limit = 50) override;
void InsertNewCharacter(const ICharInfo::Info info) override;
void InsertCharacterXml(const LWOOBJID accountId, const std::string_view lxfml) override;
std::vector<LWOOBJID> GetAccountCharacterIds(LWOOBJID accountId) override;
@@ -65,6 +76,12 @@ public:
void UpdateLastLoggedInCharacter(const LWOOBJID characterId) override;
void SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) override;
std::optional<IPetNames::Info> GetPetNameInfo(const LWOOBJID& petId) override;
// Pet name moderation
std::vector<IPetNames::DetailedInfo> GetAllPetNames() override;
std::vector<IPetNames::DetailedInfo> GetPetNamesByStatus(int32_t status) override;
void SetPetApprovalStatus(const LWOOBJID& petId, int32_t status) override;
uint32_t GetPendingPetNamesCount() override;
std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override;
void UpdatePropertyModerationInfo(const IProperty::Info& info) override;
void UpdatePropertyDetails(const IProperty::Info& info) override;
@@ -77,6 +94,15 @@ public:
void RemoveModel(const LWOOBJID& modelId) override;
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override;
// Bug reports (dashboard)
std::vector<IBugReports::DetailedInfo> GetAllBugReports() override;
std::vector<IBugReports::DetailedInfo> GetUnresolvedBugReports() override;
std::vector<IBugReports::DetailedInfo> GetResolvedBugReports() override;
std::optional<IBugReports::DetailedInfo> GetBugReportById(const uint64_t reportId) override;
void ResolveBugReport(const uint64_t reportId, const uint32_t resolvedById, const std::string_view resolution) override;
uint32_t GetBugReportCount() override;
uint32_t GetUnresolvedBugReportCount() override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const MailInfo& mail) override;
void InsertNewUgcModel(
@@ -91,6 +117,7 @@ public:
void DeleteMail(const uint64_t mailId) override;
void ClaimMailItem(const uint64_t mailId) override;
void InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) override;
std::vector<ICommandLog::Entry> GetCommandLogs(uint32_t limit = 100) override;
void UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) override;
void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override;
@@ -101,6 +128,16 @@ public:
void InsertDefaultPersistentId() override;
std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override;
std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override;
// Play keys management
std::vector<IPlayKeys::Info> GetAllPlayKeys() override;
std::optional<IPlayKeys::Info> GetPlayKeyById(const uint32_t playkeyId) override;
std::optional<IPlayKeys::Info> GetPlayKeyByString(const std::string_view key_string) override;
bool ConsumePlayKeyUsage(const uint32_t playkeyId) override;
void CreatePlayKey(const std::string_view key_string, uint32_t uses, const std::string_view notes) override;
void UpdatePlayKey(const uint32_t playkeyId, uint32_t uses, bool active, const std::string_view notes) override;
void DeletePlayKey(const uint32_t playkeyId) override;
uint32_t GetPlayKeyCount() override;
std::vector<IUgc::Model> GetUgcModels(const LWOOBJID& propertyId) override;
void AddIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) override;
void RemoveIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) override;
@@ -111,6 +148,7 @@ public:
std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const LWOOBJID characterId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
void UpdateAccountPlayKey(const uint32_t accountId, const uint32_t playKeyId) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override;
@@ -124,10 +162,45 @@ public:
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<LWOOBJID> characterId) override;
void DeleteUgcBuild(const LWOOBJID bigId) override;
uint32_t GetAccountCount() override;
uint32_t GetBannedAccountCount() override;
uint32_t GetLockedAccountCount() override;
bool IsNameInUse(const std::string_view name) override;
uint32_t GetCharacterCount() override;
std::vector<ICharInfo::Info> GetAllCharactersPaginated(uint32_t offset, uint32_t limit, const std::string& orderColumn, const std::string& orderDir) override;
std::vector<ICharInfo::Info> GetCharactersWithPendingNames() override;
// Character management additions
void UpdateCharacterPermissions(const LWOOBJID characterId, ePermissionMap permissions) override;
void SetCharacterNeedsRename(const LWOOBJID characterId, bool needsRename) override;
std::optional<ICharInfo::Stats> GetCharacterStats(const LWOOBJID characterId) override;
std::vector<ICharInfo::InventoryItem> GetCharacterInventory(const LWOOBJID characterId) override;
std::vector<ICharInfo::Activity> GetCharacterActivity(const LWOOBJID characterId, uint32_t limit = 50) override;
void RescueCharacter(const LWOOBJID characterId, uint32_t zoneId) override;
std::optional<IPropertyContents::Model> GetModel(const LWOOBJID modelID) override;
std::optional<IUgc::Model> GetUgcModel(const LWOOBJID ugcId) override;
std::optional<IProperty::Info> GetPropertyInfo(const LWOOBJID id) override;
// Property listing/approval (dashboard)
std::vector<IProperty::Info> GetAllProperties() override;
std::vector<IProperty::Info> GetPropertiesByApprovalStatus(uint32_t approved) override;
uint32_t GetPropertyCount() override;
uint32_t GetUnapprovedPropertyCount() override;
// Dashboard Audit Log
void InsertAuditLog(const std::string_view ip_address, const std::string_view endpoint,
const std::string_view method, const std::string_view user_agent, int32_t response_code) override;
std::vector<IDashboardAuditLog::AuditLogEntry> GetRecentAuditLogs(uint32_t limit) override;
std::vector<IDashboardAuditLog::AuditLogEntry> GetAuditLogsByIP(const std::string_view ip_address, uint32_t limit) override;
void CleanupOldAuditLogs(uint32_t days_to_keep) override;
void InsertAdminActionLog(uint32_t adminAccountId, const std::string_view action,
const std::string_view targetType, uint64_t targetId,
const std::string_view details) override;
std::vector<IDashboardAuditLog::AdminActionLog> GetAuditLogs(uint32_t limit = 100) override;
// Dashboard Config
std::optional<std::string> GetDashboardConfig(const std::string_view config_key) override;
void SetDashboardConfig(const std::string_view config_key, const std::string_view config_value) override;
private:
CppSQLite3Statement CreatePreppedStmt(const std::string& query);

View File

@@ -42,9 +42,91 @@ void SQLiteDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGameM
ExecuteUpdate("UPDATE accounts SET gm_level = ? WHERE id = ?;", static_cast<int32_t>(gmLevel), accountId);
}
void SQLiteDatabase::UpdateAccountPlayKey(const uint32_t accountId, const uint32_t playKeyId) {
ExecuteUpdate("UPDATE accounts SET play_key_id = ? WHERE id = ?;", playKeyId, accountId);
}
uint32_t SQLiteDatabase::GetAccountCount() {
auto [_, res] = ExecuteSelect("SELECT COUNT(*) as count FROM accounts;");
if (res.eof()) return 0;
return res.getIntField("count");
}
uint32_t SQLiteDatabase::GetBannedAccountCount() {
auto [_, res] = ExecuteSelect("SELECT COUNT(*) as count FROM accounts WHERE banned = 1;");
if (res.eof()) return 0;
return res.getIntField("count");
}
uint32_t SQLiteDatabase::GetLockedAccountCount() {
auto [_, res] = ExecuteSelect("SELECT COUNT(*) as count FROM accounts WHERE locked = 1;");
if (res.eof()) return 0;
return res.getIntField("count");
}
std::vector<IAccounts::ListInfo> SQLiteDatabase::GetAllAccounts() {
std::vector<IAccounts::ListInfo> out;
auto [stmt, res] = ExecuteSelect("SELECT id, name, gm_level, banned, locked, mute_expire, play_key_id FROM accounts ORDER BY id ASC;");
while (!res.eof()) {
IAccounts::ListInfo info;
info.id = res.getIntField("id");
info.name = res.getStringField("name");
info.gm_level = static_cast<eGameMasterLevel>(res.getIntField("gm_level"));
info.banned = res.getIntField("banned");
info.locked = res.getIntField("locked");
info.mute_expire = static_cast<uint64_t>(res.getInt64Field("mute_expire"));
info.play_key_id = res.getIntField("play_key_id");
out.push_back(info);
res.nextRow();
}
return out;
}
void SQLiteDatabase::UpdateAccountLock(const uint32_t accountId, const bool locked) {
ExecuteUpdate("UPDATE accounts SET locked = ? WHERE id = ?;", locked, accountId);
}
std::optional<IAccounts::DetailedInfo> SQLiteDatabase::GetAccountById(const uint32_t accountId) {
auto [_, result] = ExecuteSelect(
"SELECT id, name, email, gm_level, banned, locked, mute_expire, play_key_id, created_at FROM accounts WHERE id = ? LIMIT 1;",
accountId
);
if (result.eof()) {
return std::nullopt;
}
IAccounts::DetailedInfo info;
info.id = result.getIntField("id");
info.name = result.getStringField("name");
info.email = result.getStringField("email");
info.gm_level = static_cast<eGameMasterLevel>(result.getIntField("gm_level"));
info.banned = result.getIntField("banned") != 0;
info.locked = result.getIntField("locked") != 0;
info.mute_expire = static_cast<uint64_t>(result.getInt64Field("mute_expire"));
info.play_key_id = result.getIntField("play_key_id");
info.created_at = static_cast<uint64_t>(result.getInt64Field("created_at"));
return info;
}
void SQLiteDatabase::UpdateAccountEmail(const uint32_t accountId, const std::string_view email) {
ExecuteUpdate("UPDATE accounts SET email = ? WHERE id = ?;", email, accountId);
}
void SQLiteDatabase::DeleteAccount(const uint32_t accountId) {
// Delete all associated data first
ExecuteDelete("DELETE FROM char_info WHERE account_id = ?;", accountId);
ExecuteDelete("DELETE FROM accounts WHERE id = ?;", accountId);
}
std::vector<IAccounts::SessionInfo> SQLiteDatabase::GetAccountSessions(const uint32_t accountId, uint32_t limit) {
// account_sessions table doesn't exist in the current schema
// Session tracking would need to be implemented separately
return {};
}

View File

@@ -4,3 +4,63 @@ void SQLiteDatabase::UpdateActivityLog(const LWOOBJID characterId, const eActivi
ExecuteInsert("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);",
characterId, static_cast<uint32_t>(activityType), static_cast<uint32_t>(time(NULL)), mapId);
}
std::vector<IActivityLog::Entry> SQLiteDatabase::GetRecentActivity(const uint32_t limit) {
std::vector<IActivityLog::Entry> out;
auto [stmt, res] = ExecuteSelect("SELECT character_id, activity, time, map_id FROM activity_log ORDER BY time DESC LIMIT ?;", limit);
while (!res.eof()) {
IActivityLog::Entry e;
e.characterId = static_cast<LWOOBJID>(res.getInt64Field("character_id"));
e.activity = static_cast<eActivityType>(res.getIntField("activity"));
e.timestamp = static_cast<uint32_t>(res.getIntField("time"));
e.mapId = static_cast<LWOMAPID>(res.getIntField("map_id"));
out.push_back(e);
res.nextRow();
}
return out;
}
uint32_t SQLiteDatabase::GetActivityLogCount() {
auto [_, res] = ExecuteSelect("SELECT COUNT(*) as count FROM activity_log;");
if (res.eof()) return 0;
return res.getIntField("count");
}
std::vector<IActivityLog::Entry> SQLiteDatabase::GetActivityLogPaginated(
uint32_t offset,
uint32_t limit,
const std::string& orderColumn,
const std::string& orderDir
) {
std::vector<IActivityLog::Entry> out;
// Validate orderColumn to prevent SQL injection
std::string validColumn = "time";
if (orderColumn == "character_id" || orderColumn == "activity" || orderColumn == "map_id" || orderColumn == "time") {
validColumn = orderColumn;
}
// Validate orderDir
std::string validDir = (orderDir == "ASC" || orderDir == "asc") ? "ASC" : "DESC";
// Build query - can't use prepared statement for ORDER BY clause
std::string query = "SELECT character_id, activity, time, map_id FROM activity_log ORDER BY " +
validColumn + " " + validDir + " LIMIT ? OFFSET ?;";
auto [stmt, res] = ExecuteSelect(query, limit, offset);
while (!res.eof()) {
IActivityLog::Entry e;
e.characterId = static_cast<LWOOBJID>(res.getInt64Field("character_id"));
e.activity = static_cast<eActivityType>(res.getIntField("activity"));
e.timestamp = static_cast<uint32_t>(res.getIntField("time"));
e.mapId = static_cast<LWOMAPID>(res.getIntField("map_id"));
out.push_back(e);
res.nextRow();
}
return out;
}

View File

@@ -4,3 +4,127 @@ void SQLiteDatabase::InsertNewBugReport(const IBugReports::Info& info) {
ExecuteInsert("INSERT INTO `bug_reports`(body, client_version, other_player_id, selection, reporter_id) VALUES (?, ?, ?, ?, ?)",
info.body, info.clientVersion, info.otherPlayer, info.selection, info.characterId);
}
std::vector<IBugReports::DetailedInfo> SQLiteDatabase::GetAllBugReports() {
std::vector<IBugReports::DetailedInfo> out;
auto [stmt, res] = ExecuteSelect(
"SELECT id, body, client_version, other_player_id, selection, reporter_id, "
"strftime('%s', submitted) as submitted, strftime('%s', resolved_time) as resolved_time, "
"resolved_by_id, resolution FROM bug_reports ORDER BY id DESC;"
);
while (!res.eof()) {
IBugReports::DetailedInfo info;
info.id = static_cast<uint64_t>(res.getInt64Field("id"));
info.body = res.getStringField("body");
info.clientVersion = res.getStringField("client_version");
info.otherPlayer = res.getStringField("other_player_id");
info.selection = res.getStringField("selection");
info.characterId = static_cast<LWOOBJID>(res.getInt64Field("reporter_id"));
info.submitted = static_cast<uint64_t>(res.getInt64Field("submitted"));
info.resolved_time = res.fieldIsNull("resolved_time") ? 0 : static_cast<uint64_t>(res.getInt64Field("resolved_time"));
info.resolved_by_id = res.fieldIsNull("resolved_by_id") ? 0 : res.getIntField("resolved_by_id");
info.resolution = res.getStringField("resolution");
out.push_back(info);
res.nextRow();
}
return out;
}
std::vector<IBugReports::DetailedInfo> SQLiteDatabase::GetUnresolvedBugReports() {
std::vector<IBugReports::DetailedInfo> out;
auto [stmt, res] = ExecuteSelect(
"SELECT id, body, client_version, other_player_id, selection, reporter_id, "
"strftime('%s', submitted) as submitted FROM bug_reports WHERE resolved_time IS NULL ORDER BY id DESC;"
);
while (!res.eof()) {
IBugReports::DetailedInfo info;
info.id = static_cast<uint64_t>(res.getInt64Field("id"));
info.body = res.getStringField("body");
info.clientVersion = res.getStringField("client_version");
info.otherPlayer = res.getStringField("other_player_id");
info.selection = res.getStringField("selection");
info.characterId = static_cast<LWOOBJID>(res.getInt64Field("reporter_id"));
info.submitted = static_cast<uint64_t>(res.getInt64Field("submitted"));
info.resolved_time = 0;
info.resolved_by_id = 0;
info.resolution = "";
out.push_back(info);
res.nextRow();
}
return out;
}
std::vector<IBugReports::DetailedInfo> SQLiteDatabase::GetResolvedBugReports() {
std::vector<IBugReports::DetailedInfo> out;
auto [stmt, res] = ExecuteSelect(
"SELECT id, body, client_version, other_player_id, selection, reporter_id, "
"strftime('%s', submitted) as submitted, strftime('%s', resolved_time) as resolved_time, "
"resolved_by_id, resolution FROM bug_reports WHERE resolved_time IS NOT NULL ORDER BY id DESC;"
);
while (!res.eof()) {
IBugReports::DetailedInfo info;
info.id = static_cast<uint64_t>(res.getInt64Field("id"));
info.body = res.getStringField("body");
info.clientVersion = res.getStringField("client_version");
info.otherPlayer = res.getStringField("other_player_id");
info.selection = res.getStringField("selection");
info.characterId = static_cast<LWOOBJID>(res.getInt64Field("reporter_id"));
info.submitted = static_cast<uint64_t>(res.getInt64Field("submitted"));
info.resolved_time = static_cast<uint64_t>(res.getInt64Field("resolved_time"));
info.resolved_by_id = res.getIntField("resolved_by_id");
info.resolution = res.getStringField("resolution");
out.push_back(info);
res.nextRow();
}
return out;
}
std::optional<IBugReports::DetailedInfo> SQLiteDatabase::GetBugReportById(const uint64_t reportId) {
auto [_, result] = ExecuteSelect(
"SELECT id, body, client_version, other_player_id, selection, reporter_id, "
"strftime('%s', submitted) as submitted, strftime('%s', resolved_time) as resolved_time, "
"resolved_by_id, resolution FROM bug_reports WHERE id = ? LIMIT 1;",
reportId
);
if (result.eof()) {
return std::nullopt;
}
IBugReports::DetailedInfo info;
info.id = static_cast<uint64_t>(result.getInt64Field("id"));
info.body = result.getStringField("body");
info.clientVersion = result.getStringField("client_version");
info.otherPlayer = result.getStringField("other_player_id");
info.selection = result.getStringField("selection");
info.characterId = static_cast<LWOOBJID>(result.getInt64Field("reporter_id"));
info.submitted = static_cast<uint64_t>(result.getInt64Field("submitted"));
info.resolved_time = result.fieldIsNull("resolved_time") ? 0 : static_cast<uint64_t>(result.getInt64Field("resolved_time"));
info.resolved_by_id = result.fieldIsNull("resolved_by_id") ? 0 : result.getIntField("resolved_by_id");
info.resolution = result.getStringField("resolution");
return info;
}
void SQLiteDatabase::ResolveBugReport(const uint64_t reportId, const uint32_t resolvedById, const std::string_view resolution) {
ExecuteUpdate(
"UPDATE bug_reports SET resolved_time = datetime('now'), resolved_by_id = ?, resolution = ? WHERE id = ?;",
resolvedById, resolution, reportId
);
}
uint32_t SQLiteDatabase::GetBugReportCount() {
auto [_, res] = ExecuteSelect("SELECT COUNT(*) as count FROM bug_reports;");
return res.eof() ? 0 : res.getIntField("count");
}
uint32_t SQLiteDatabase::GetUnresolvedBugReportCount() {
auto [_, res] = ExecuteSelect("SELECT COUNT(*) as count FROM bug_reports WHERE resolved_time IS NULL;");
return res.eof() ? 0 : res.getIntField("count");
}

View File

@@ -7,6 +7,8 @@ set(DDATABASES_DATABASES_SQLITE_TABLES_SOURCES
"CharInfo.cpp"
"CharXml.cpp"
"CommandLog.cpp"
"DashboardAuditLog.cpp"
"DashboardConfig.cpp"
"Friends.cpp"
"IgnoreList.cpp"
"Leaderboard.cpp"

View File

@@ -83,3 +83,117 @@ bool SQLiteDatabase::IsNameInUse(const std::string_view name) {
return !result.eof();
}
uint32_t SQLiteDatabase::GetCharacterCount() {
auto [_, res] = ExecuteSelect("SELECT COUNT(*) as count FROM charinfo;");
if (res.eof()) return 0;
return res.getIntField("count");
}
std::vector<ICharInfo::Info> SQLiteDatabase::GetAllCharactersPaginated(
uint32_t offset,
uint32_t limit,
const std::string& orderColumn,
const std::string& orderDir
) {
std::vector<ICharInfo::Info> out;
// Validate orderColumn to prevent SQL injection
std::string validColumn = "id";
if (orderColumn == "name" || orderColumn == "account_id" || orderColumn == "id" || orderColumn == "last_login") {
validColumn = orderColumn;
}
// Validate orderDir
std::string validDir = (orderDir == "ASC" || orderDir == "asc") ? "ASC" : "DESC";
// Build query
std::string query = "SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo ORDER BY " +
validColumn + " " + validDir + " LIMIT ? OFFSET ?;";
auto [stmt, res] = ExecuteSelect(query, limit, offset);
while (!res.eof()) {
ICharInfo::Info info;
info.id = res.getInt64Field("id");
info.name = res.getStringField("name");
info.pendingName = res.getStringField("pending_name");
info.needsRename = res.getIntField("needs_rename");
info.cloneId = res.getInt64Field("prop_clone_id");
info.accountId = res.getIntField("account_id");
info.permissionMap = static_cast<ePermissionMap>(res.getIntField("permission_map"));
out.push_back(info);
res.nextRow();
}
return out;
}
std::vector<ICharInfo::Info> SQLiteDatabase::GetCharactersWithPendingNames() {
std::vector<ICharInfo::Info> out;
auto [stmt, res] = ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE pending_name != '' ORDER BY id ASC;");
while (!res.eof()) {
ICharInfo::Info info;
info.id = res.getInt64Field("id");
info.name = res.getStringField("name");
info.pendingName = res.getStringField("pending_name");
info.needsRename = res.getIntField("needs_rename");
info.cloneId = res.getInt64Field("prop_clone_id");
info.accountId = res.getIntField("account_id");
info.permissionMap = static_cast<ePermissionMap>(res.getIntField("permission_map"));
out.push_back(info);
res.nextRow();
}
return out;
}
// SQLite-specific implementations added: UpdateCharacterPermissions and SetCharacterNeedsRename
void SQLiteDatabase::UpdateCharacterPermissions(const LWOOBJID characterId, const ePermissionMap permissions) {
ExecuteUpdate("UPDATE charinfo SET permission_map = ? WHERE id = ?;", static_cast<uint64_t>(permissions), characterId);
}
void SQLiteDatabase::SetCharacterNeedsRename(const LWOOBJID characterId, const bool needsRename) {
ExecuteUpdate("UPDATE charinfo SET needs_rename = ? WHERE id = ?;", needsRename ? 1 : 0, characterId);
}
std::optional<ICharInfo::Stats> SQLiteDatabase::GetCharacterStats(const LWOOBJID characterId) {
// char_stats table doesn't exist in the current schema
// Stats would need to be parsed from charxml or a new table created
return std::nullopt;
}
std::vector<ICharInfo::InventoryItem> SQLiteDatabase::GetCharacterInventory(const LWOOBJID characterId) {
// Inventory data is stored in charxml, not a separate table
// Would need to parse the XML to extract inventory items
// Returning empty for now - implement XML parsing if needed
return {};
}
std::vector<ICharInfo::Activity> SQLiteDatabase::GetCharacterActivity(const LWOOBJID characterId, uint32_t limit) {
auto [_, res] = ExecuteSelect(
"SELECT time, activity, map_id FROM activity_log WHERE character_id = ? ORDER BY time DESC LIMIT ?;",
characterId, limit
);
std::vector<ICharInfo::Activity> activities;
while (!res.eof()) {
ICharInfo::Activity activity;
activity.timestamp = res.getInt64Field("time");
activity.activity = static_cast<eActivityType>(res.getIntField("activity"));
activity.mapId = res.getIntField("map_id");
activities.push_back(activity);
res.nextRow();
}
return activities;
}
void SQLiteDatabase::RescueCharacter(const LWOOBJID characterId, uint32_t zoneId) {
// The rescue is now handled by the chat server API which kicks the player
// and modifies the XML after the player data is saved
// This database method is intentionally a no-op as the actual work
// is done via DashboardHelpers::RescueCharacter()
}

View File

@@ -3,3 +3,20 @@
void SQLiteDatabase::InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) {
ExecuteInsert("INSERT INTO command_log (character_id, command) VALUES (?, ?);", characterId, command);
}
std::vector<ICommandLog::Entry> SQLiteDatabase::GetCommandLogs(uint32_t limit) {
auto [_, result] = ExecuteSelect("SELECT id, character_id, command FROM command_log ORDER BY id DESC LIMIT ?;", limit);
std::vector<ICommandLog::Entry> logs;
while (!result.eof()) {
ICommandLog::Entry entry;
entry.timestamp = 0; // Timestamp column doesn't exist in command_log table
entry.characterId = result.getInt64Field("character_id");
entry.command = result.getStringField("command");
entry.arguments = ""; // Arguments not currently stored in DB
logs.push_back(entry);
result.nextRow();
}
return logs;
}

View File

@@ -0,0 +1,67 @@
#include "SQLiteDatabase.h"
#include <ctime>
void SQLiteDatabase::InsertAuditLog(const std::string_view ip, const std::string_view endpoint,
const std::string_view method, const std::string_view user_agent,
int32_t response_code) {
uint64_t now = static_cast<uint64_t>(std::time(nullptr));
ExecuteInsert("INSERT INTO dashboard_audit_log (timestamp, ip_address, endpoint, method, user_agent, response_code) VALUES (?, ?, ?, ?, ?, ?);",
now, ip, endpoint, method, user_agent, response_code);
}
std::vector<IDashboardAuditLog::AuditLogEntry> SQLiteDatabase::GetRecentAuditLogs(uint32_t limit) {
auto [_, result] = ExecuteSelect("SELECT id, timestamp, ip_address, endpoint, method, user_agent, response_code FROM dashboard_audit_log ORDER BY timestamp DESC LIMIT ?;", limit);
std::vector<IDashboardAuditLog::AuditLogEntry> logs;
while (result.nextRow()) {
IDashboardAuditLog::AuditLogEntry entry;
entry.id = result.getInt64Field(0);
entry.timestamp = result.getInt64Field(1);
entry.ip_address = result.getStringField(2);
entry.endpoint = result.getStringField(3);
entry.method = result.getStringField(4);
entry.user_agent = result.getStringField(5);
entry.response_code = result.getIntField(6);
logs.push_back(entry);
}
return logs;
}
std::vector<IDashboardAuditLog::AuditLogEntry> SQLiteDatabase::GetAuditLogsByIP(const std::string_view ip, uint32_t limit) {
auto [_, result] = ExecuteSelect("SELECT id, timestamp, ip_address, endpoint, method, user_agent, response_code FROM dashboard_audit_log WHERE ip_address = ? ORDER BY timestamp DESC LIMIT ?;", ip, limit);
std::vector<IDashboardAuditLog::AuditLogEntry> logs;
while (result.nextRow()) {
IDashboardAuditLog::AuditLogEntry entry;
entry.id = result.getInt64Field(0);
entry.timestamp = result.getInt64Field(1);
entry.ip_address = result.getStringField(2);
entry.endpoint = result.getStringField(3);
entry.method = result.getStringField(4);
entry.user_agent = result.getStringField(5);
entry.response_code = result.getIntField(6);
logs.push_back(entry);
}
return logs;
}
void SQLiteDatabase::CleanupOldAuditLogs(uint32_t days_to_keep) {
uint64_t cutoff_time = static_cast<uint64_t>(std::time(nullptr)) - (days_to_keep * 86400);
ExecuteDelete("DELETE FROM dashboard_audit_log WHERE timestamp < ?;", cutoff_time);
}
void SQLiteDatabase::InsertAdminActionLog(uint32_t adminAccountId, const std::string_view action,
const std::string_view targetType, uint64_t targetId,
const std::string_view details) {
// dashboard_admin_action_log table doesn't exist
// Admin actions could be logged to dashboard_audit_log or a new table needs to be created
// For now, this is a no-op
}
std::vector<IDashboardAuditLog::AdminActionLog> SQLiteDatabase::GetAuditLogs(uint32_t limit) {
// dashboard_admin_action_log table doesn't exist
// Would need to create a new table or use dashboard_audit_log with additional columns
return {};
}

View File

@@ -0,0 +1,15 @@
#include "SQLiteDatabase.h"
std::optional<std::string> SQLiteDatabase::GetDashboardConfig(const std::string_view key) {
auto [_, result] = ExecuteSelect("SELECT config_value FROM dashboard_config WHERE config_key = ?;", key);
if (result.nextRow()) {
return result.getStringField(0);
}
return std::nullopt;
}
void SQLiteDatabase::SetDashboardConfig(const std::string_view key, const std::string_view value) {
ExecuteInsert("INSERT OR REPLACE INTO dashboard_config (config_key, config_value, updated_at) VALUES (?, ?, strftime('%s', 'now'));", key, value);
}

View File

@@ -24,3 +24,46 @@ std::optional<IPetNames::Info> SQLiteDatabase::GetPetNameInfo(const LWOOBJID& pe
return toReturn;
}
std::vector<IPetNames::DetailedInfo> SQLiteDatabase::GetAllPetNames() {
std::vector<IPetNames::DetailedInfo> out;
auto [stmt, res] = ExecuteSelect("SELECT id, pet_name, approved, owner_id FROM pet_names ORDER BY id DESC;");
while (!res.eof()) {
IPetNames::DetailedInfo info;
info.id = static_cast<LWOOBJID>(res.getInt64Field("id"));
info.petName = res.getStringField("pet_name");
info.approvalStatus = res.getIntField("approved");
info.ownerId = static_cast<LWOOBJID>(res.getInt64Field("owner_id"));
out.push_back(info);
res.nextRow();
}
return out;
}
std::vector<IPetNames::DetailedInfo> SQLiteDatabase::GetPetNamesByStatus(int32_t status) {
std::vector<IPetNames::DetailedInfo> out;
auto [stmt, res] = ExecuteSelect("SELECT id, pet_name, approved, owner_id FROM pet_names WHERE approved = ? ORDER BY id DESC;", status);
while (!res.eof()) {
IPetNames::DetailedInfo info;
info.id = static_cast<LWOOBJID>(res.getInt64Field("id"));
info.petName = res.getStringField("pet_name");
info.approvalStatus = res.getIntField("approved");
info.ownerId = static_cast<LWOOBJID>(res.getInt64Field("owner_id"));
out.push_back(info);
res.nextRow();
}
return out;
}
void SQLiteDatabase::SetPetApprovalStatus(const LWOOBJID& petId, int32_t status) {
ExecuteUpdate("UPDATE pet_names SET approved = ? WHERE id = ?;", status, petId);
}
uint32_t SQLiteDatabase::GetPendingPetNamesCount() {
auto [_, res] = ExecuteSelect("SELECT COUNT(*) as count FROM pet_names WHERE approved = 1;");
return res.eof() ? 0 : res.getIntField("count");
}

View File

@@ -9,3 +9,118 @@ std::optional<bool> SQLiteDatabase::IsPlaykeyActive(const int32_t playkeyId) {
return keyCheckRes.getIntField("active");
}
std::vector<IPlayKeys::Info> SQLiteDatabase::GetAllPlayKeys() {
std::vector<IPlayKeys::Info> out;
auto [stmt, res] = ExecuteSelect(
"SELECT id, key_string, key_uses, times_used, active, notes, strftime('%s', created_at) as created_at "
"FROM play_keys ORDER BY id DESC;"
);
while (!res.eof()) {
IPlayKeys::Info info;
info.id = res.getIntField("id");
info.key_string = res.getStringField("key_string");
info.key_uses = res.getIntField("key_uses");
info.times_used = res.getIntField("times_used");
info.active = res.getIntField("active") != 0;
info.notes = res.getStringField("notes");
info.created_at = static_cast<uint64_t>(res.getInt64Field("created_at"));
out.push_back(info);
res.nextRow();
}
return out;
}
std::optional<IPlayKeys::Info> SQLiteDatabase::GetPlayKeyById(const uint32_t playkeyId) {
auto [_, result] = ExecuteSelect(
"SELECT id, key_string, key_uses, times_used, active, notes, strftime('%s', created_at) as created_at "
"FROM play_keys WHERE id = ? LIMIT 1;",
playkeyId
);
if (result.eof()) {
return std::nullopt;
}
IPlayKeys::Info info;
info.id = result.getIntField("id");
info.key_string = result.getStringField("key_string");
info.key_uses = result.getIntField("key_uses");
info.times_used = result.getIntField("times_used");
info.active = result.getIntField("active") != 0;
info.notes = result.getStringField("notes");
info.created_at = static_cast<uint64_t>(result.getInt64Field("created_at"));
return info;
}
std::optional<IPlayKeys::Info> SQLiteDatabase::GetPlayKeyByString(const std::string_view key_string) {
auto [_, result] = ExecuteSelect(
"SELECT id, key_string, key_uses, times_used, active, notes, strftime('%s', created_at) as created_at "
"FROM play_keys WHERE key_string = ? LIMIT 1;",
key_string
);
if (result.eof()) {
return std::nullopt;
}
IPlayKeys::Info info;
info.id = result.getIntField("id");
info.key_string = result.getStringField("key_string");
info.key_uses = result.getIntField("key_uses");
info.times_used = result.getIntField("times_used");
info.active = result.getIntField("active") != 0;
info.notes = result.getStringField("notes");
info.created_at = static_cast<uint64_t>(result.getInt64Field("created_at"));
return info;
}
bool SQLiteDatabase::ConsumePlayKeyUsage(const uint32_t playkeyId) {
// Atomically check times_used < key_uses and increment times_used. If uses exhausted, deactivate.
auto [stmt, res] = ExecuteSelect("SELECT key_uses, times_used, active FROM play_keys WHERE id = ? LIMIT 1;", playkeyId);
if (res.eof()) return false;
int key_uses = res.getIntField("key_uses");
int times_used = res.getIntField("times_used");
int active = res.getIntField("active");
if (!active) return false;
if (times_used >= key_uses) return false;
// Increment times_used
ExecuteUpdate("UPDATE play_keys SET times_used = times_used + 1 WHERE id = ?;", playkeyId);
// If we've reached the limit, deactivate
if (times_used + 1 >= key_uses) {
ExecuteUpdate("UPDATE play_keys SET active = 0 WHERE id = ?;", playkeyId);
}
return true;
}
void SQLiteDatabase::CreatePlayKey(const std::string_view key_string, uint32_t uses, const std::string_view notes) {
ExecuteInsert(
"INSERT INTO play_keys (key_string, key_uses, times_used, active, notes) VALUES (?, ?, 0, 1, ?);",
key_string, uses, notes
);
}
void SQLiteDatabase::UpdatePlayKey(const uint32_t playkeyId, uint32_t uses, bool active, const std::string_view notes) {
ExecuteUpdate(
"UPDATE play_keys SET key_uses = ?, active = ?, notes = ? WHERE id = ?;",
uses, active ? 1 : 0, notes, playkeyId
);
}
void SQLiteDatabase::DeletePlayKey(const uint32_t playkeyId) {
ExecuteDelete("DELETE FROM play_keys WHERE id = ?;", playkeyId);
}
uint32_t SQLiteDatabase::GetPlayKeyCount() {
auto [_, res] = ExecuteSelect("SELECT COUNT(*) as count FROM play_keys;");
return res.eof() ? 0 : res.getIntField("count");
}

View File

@@ -193,10 +193,53 @@ std::optional<IProperty::Info> SQLiteDatabase::GetPropertyInfo(const LWOOBJID id
auto [_, propertyEntry] = ExecuteSelect(
"SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, last_updated, time_claimed, reputation, mod_approved, performance_cost "
"FROM properties WHERE id = ?;", id);
if (propertyEntry.eof()) {
return std::nullopt;
}
return ReadPropertyInfo(propertyEntry);
}
std::vector<IProperty::Info> SQLiteDatabase::GetAllProperties() {
std::vector<IProperty::Info> out;
auto [stmt, res] = ExecuteSelect(
"SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, "
"last_updated, time_claimed, reputation, mod_approved, performance_cost "
"FROM properties ORDER BY id DESC;"
);
while (!res.eof()) {
out.push_back(ReadPropertyInfo(res));
res.nextRow();
}
return out;
}
std::vector<IProperty::Info> SQLiteDatabase::GetPropertiesByApprovalStatus(uint32_t approved) {
std::vector<IProperty::Info> out;
auto [stmt, res] = ExecuteSelect(
"SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, "
"last_updated, time_claimed, reputation, mod_approved, performance_cost "
"FROM properties WHERE mod_approved = ? ORDER BY id DESC;",
approved
);
while (!res.eof()) {
out.push_back(ReadPropertyInfo(res));
res.nextRow();
}
return out;
}
uint32_t SQLiteDatabase::GetPropertyCount() {
auto [_, res] = ExecuteSelect("SELECT COUNT(*) as count FROM properties;");
return res.eof() ? 0 : res.getIntField("count");
}
uint32_t SQLiteDatabase::GetUnapprovedPropertyCount() {
auto [_, res] = ExecuteSelect("SELECT COUNT(*) as count FROM properties WHERE mod_approved = 0;");
return res.eof() ? 0 : res.getIntField("count");
}

View File

@@ -56,6 +56,18 @@ void TestSQLDatabase::UpdateActivityLog(const LWOOBJID characterId, const eActiv
}
std::vector<IActivityLog::Entry> TestSQLDatabase::GetRecentActivity(const uint32_t limit) {
return {};
}
uint32_t TestSQLDatabase::GetActivityLogCount() {
return {};
}
std::vector<IActivityLog::Entry> TestSQLDatabase::GetActivityLogPaginated(uint32_t offset, uint32_t limit, const std::string& orderColumn, const std::string& orderDir) {
return {};
}
void TestSQLDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
}
@@ -192,6 +204,115 @@ void TestSQLDatabase::InsertNewMail(const MailInfo& mail) {
}
// PlayKeys (dashboard)
std::vector<IPlayKeys::Info> TestSQLDatabase::GetAllPlayKeys() {
return {};
}
std::optional<IPlayKeys::Info> TestSQLDatabase::GetPlayKeyById(const uint32_t playkeyId) {
return {};
}
std::optional<IPlayKeys::Info> TestSQLDatabase::GetPlayKeyByString(const std::string_view key_string) {
return {};
}
bool TestSQLDatabase::ConsumePlayKeyUsage(const uint32_t playkeyId) {
return false;
}
void TestSQLDatabase::CreatePlayKey(const std::string_view key, uint32_t accountId, const std::string_view notes) {
}
void TestSQLDatabase::UpdatePlayKey(const uint32_t playkeyId, uint32_t accountId, bool active, const std::string_view notes) {
}
void TestSQLDatabase::DeletePlayKey(const uint32_t playkeyId) {
}
uint32_t TestSQLDatabase::GetPlayKeyCount() {
return 0;
}
// Bug reports (dashboard)
std::vector<IBugReports::DetailedInfo> TestSQLDatabase::GetAllBugReports() {
return {};
}
std::vector<IBugReports::DetailedInfo> TestSQLDatabase::GetUnresolvedBugReports() {
return {};
}
std::vector<IBugReports::DetailedInfo> TestSQLDatabase::GetResolvedBugReports() {
return {};
}
std::optional<IBugReports::DetailedInfo> TestSQLDatabase::GetBugReportById(const uint64_t reportId) {
return {};
}
void TestSQLDatabase::ResolveBugReport(const uint64_t reportId, const uint32_t resolverAccountId, const std::string_view resolutionNotes) {
}
uint32_t TestSQLDatabase::GetBugReportCount() {
return 0;
}
uint32_t TestSQLDatabase::GetUnresolvedBugReportCount() {
return 0;
}
// Property moderation (dashboard)
std::vector<IProperty::Info> TestSQLDatabase::GetAllProperties() {
return {};
}
std::vector<IProperty::Info> TestSQLDatabase::GetPropertiesByApprovalStatus(uint32_t status) {
return {};
}
uint32_t TestSQLDatabase::GetPropertyCount() {
return 0;
}
uint32_t TestSQLDatabase::GetUnapprovedPropertyCount() {
return 0;
}
// Pet name moderation (dashboard)
std::vector<IPetNames::DetailedInfo> TestSQLDatabase::GetAllPetNames() {
return {};
}
std::vector<IPetNames::DetailedInfo> TestSQLDatabase::GetPetNamesByStatus(int32_t status) {
return {};
}
void TestSQLDatabase::SetPetApprovalStatus(const LWOOBJID& petId, int32_t status) {
}
uint32_t TestSQLDatabase::GetPendingPetNamesCount() {
return 0;
}
// Character edits (dashboard)
void TestSQLDatabase::UpdateCharacterPermissions(const LWOOBJID characterId, const ePermissionMap permissions) {
}
void TestSQLDatabase::SetCharacterNeedsRename(const LWOOBJID characterId, const bool needsRename) {
}
// Account dashboard helpers
std::optional<IAccounts::DetailedInfo> TestSQLDatabase::GetAccountById(const uint32_t accountId) {
return {};
}
void TestSQLDatabase::UpdateAccountEmail(const uint32_t accountId, const std::string_view email) {
}
void TestSQLDatabase::DeleteAccount(const uint32_t accountId) {
}
void TestSQLDatabase::InsertNewUgcModel(std::stringstream& sd0Data, const uint64_t blueprintId, const uint32_t accountId, const LWOOBJID characterId) {
}
@@ -232,6 +353,14 @@ void TestSQLDatabase::UpdateAccountBan(const uint32_t accountId, const bool bann
}
void TestSQLDatabase::UpdateAccountLock(const uint32_t accountId, const bool locked) {
}
std::vector<IAccounts::ListInfo> TestSQLDatabase::GetAllAccounts() {
return {};
}
void TestSQLDatabase::UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) {
}
@@ -300,6 +429,10 @@ void TestSQLDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGame
}
void TestSQLDatabase::UpdateAccountPlayKey(const uint32_t accountId, const uint32_t playKeyId) {
}
IObjectIdTracker::Range TestSQLDatabase::GetPersistentIdRange() {
return {};
}

View File

@@ -24,6 +24,9 @@ class TestSQLDatabase : public GameDatabase {
void AddFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) override;
void RemoveFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) override;
void UpdateActivityLog(const LWOOBJID characterId, const eActivityType activityType, const LWOMAPID mapId) override;
std::vector<IActivityLog::Entry> GetRecentActivity(const uint32_t limit) override;
uint32_t GetActivityLogCount() override;
std::vector<IActivityLog::Entry> GetActivityLogPaginated(uint32_t offset, uint32_t limit, const std::string& orderColumn, const std::string& orderDir) override;
void DeleteUgcModelData(const LWOOBJID& modelId) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
std::vector<IUgc::Model> GetAllUgcModels() override;
@@ -35,6 +38,9 @@ class TestSQLDatabase : public GameDatabase {
std::string GetCharacterXml(const LWOOBJID accountId) override;
void UpdateCharacterXml(const LWOOBJID characterId, const std::string_view lxfml) override;
std::optional<IAccounts::Info> GetAccountInfo(const std::string_view username) override;
std::vector<IAccounts::ListInfo> GetAllAccounts() override;
void UpdateAccountLock(const uint32_t accountId, const bool locked) override;
std::vector<IAccounts::SessionInfo> GetAccountSessions(const uint32_t accountId, uint32_t limit = 50) override { return {}; }
void InsertNewCharacter(const ICharInfo::Info info) override;
void InsertCharacterXml(const LWOOBJID accountId, const std::string_view lxfml) override;
std::vector<LWOOBJID> GetAccountCharacterIds(LWOOBJID accountId) override;
@@ -58,6 +64,51 @@ class TestSQLDatabase : public GameDatabase {
void InsertNewBugReport(const IBugReports::Info& info) override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const MailInfo& mail) override;
// PlayKeys (dashboard)
std::vector<IPlayKeys::Info> GetAllPlayKeys() override;
std::optional<IPlayKeys::Info> GetPlayKeyById(const uint32_t playkeyId) override;
void CreatePlayKey(const std::string_view key, uint32_t accountId, const std::string_view notes) override;
void UpdatePlayKey(const uint32_t playkeyId, uint32_t accountId, bool active, const std::string_view notes) override;
void DeletePlayKey(const uint32_t playkeyId) override;
uint32_t GetPlayKeyCount() override;
std::optional<IPlayKeys::Info> GetPlayKeyByString(const std::string_view key_string) override;
bool ConsumePlayKeyUsage(const uint32_t playkeyId) override;
// Bug reports (dashboard)
std::vector<IBugReports::DetailedInfo> GetAllBugReports() override;
std::vector<IBugReports::DetailedInfo> GetUnresolvedBugReports() override;
std::vector<IBugReports::DetailedInfo> GetResolvedBugReports() override;
std::optional<IBugReports::DetailedInfo> GetBugReportById(const uint64_t reportId) override;
void ResolveBugReport(const uint64_t reportId, const uint32_t resolverAccountId, const std::string_view resolutionNotes) override;
uint32_t GetBugReportCount() override;
uint32_t GetUnresolvedBugReportCount() override;
// Property moderation (dashboard)
std::vector<IProperty::Info> GetAllProperties() override;
std::vector<IProperty::Info> GetPropertiesByApprovalStatus(uint32_t status) override;
uint32_t GetPropertyCount() override;
uint32_t GetUnapprovedPropertyCount() override;
// Pet name moderation (dashboard)
std::vector<IPetNames::DetailedInfo> GetAllPetNames() override;
std::vector<IPetNames::DetailedInfo> GetPetNamesByStatus(int32_t status) override;
void SetPetApprovalStatus(const LWOOBJID& petId, int32_t status) override;
uint32_t GetPendingPetNamesCount() override;
// Character edits (dashboard)
void UpdateCharacterPermissions(const LWOOBJID characterId, const ePermissionMap permissions) override;
void SetCharacterNeedsRename(const LWOOBJID characterId, const bool needsRename) override;
std::optional<ICharInfo::Stats> GetCharacterStats(const LWOOBJID characterId) override { return {}; }
std::vector<ICharInfo::InventoryItem> GetCharacterInventory(const LWOOBJID characterId) override { return {}; }
std::vector<ICharInfo::Activity> GetCharacterActivity(const LWOOBJID characterId, uint32_t limit = 50) override { return {}; }
void RescueCharacter(const LWOOBJID characterId, uint32_t zoneId) override {}
// Account dashboard helpers
std::optional<IAccounts::DetailedInfo> GetAccountById(const uint32_t accountId) override;
void UpdateAccountEmail(const uint32_t accountId, const std::string_view email) override;
void DeleteAccount(const uint32_t accountId) override;
void InsertNewUgcModel(
std::stringstream& sd0Data,
const uint64_t blueprintId,
@@ -70,6 +121,7 @@ class TestSQLDatabase : public GameDatabase {
void DeleteMail(const uint64_t mailId) override;
void ClaimMailItem(const uint64_t mailId) override;
void InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) override;
std::vector<ICommandLog::Entry> GetCommandLogs(uint32_t limit = 100) override { return {}; }
void UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) override;
void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override;
@@ -90,6 +142,7 @@ class TestSQLDatabase : public GameDatabase {
std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const LWOOBJID behaviorId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
void UpdateAccountPlayKey(const uint32_t accountId, const uint32_t playKeyId) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override { return {}; };
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override { return {}; };
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override { return {}; };
@@ -103,11 +156,31 @@ class TestSQLDatabase : public GameDatabase {
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<LWOOBJID> characterId) override {};
void DeleteUgcBuild(const LWOOBJID bigId) override {};
uint32_t GetAccountCount() override { return 0; };
uint32_t GetBannedAccountCount() override { return 0; };
uint32_t GetLockedAccountCount() override { return 0; };
bool IsNameInUse(const std::string_view name) override { return false; };
uint32_t GetCharacterCount() override { return 0; };
std::vector<ICharInfo::Info> GetAllCharactersPaginated(uint32_t offset, uint32_t limit, const std::string& orderColumn, const std::string& orderDir) override { return {}; };
std::vector<ICharInfo::Info> GetCharactersWithPendingNames() override { return {}; };
std::optional<IPropertyContents::Model> GetModel(const LWOOBJID modelID) override { return {}; }
std::optional<IProperty::Info> GetPropertyInfo(const LWOOBJID id) override { return {}; }
std::optional<IUgc::Model> GetUgcModel(const LWOOBJID ugcId) override { return {}; }
// Dashboard Audit Log
void InsertAuditLog(const std::string_view ip_address, const std::string_view endpoint,
const std::string_view method, const std::string_view user_agent, int32_t response_code) override {}
std::vector<IDashboardAuditLog::AuditLogEntry> GetRecentAuditLogs(uint32_t limit) override { return {}; }
std::vector<IDashboardAuditLog::AuditLogEntry> GetAuditLogsByIP(const std::string_view ip_address, uint32_t limit) override { return {}; }
void CleanupOldAuditLogs(uint32_t days_to_keep) override {}
void InsertAdminActionLog(uint32_t adminAccountId, const std::string_view action,
const std::string_view targetType, uint64_t targetId,
const std::string_view details) override {}
std::vector<IDashboardAuditLog::AdminActionLog> GetAuditLogs(uint32_t limit = 100) override { return {}; }
// Dashboard Config
std::optional<std::string> GetDashboardConfig(const std::string_view config_key) override { return {}; }
void SetDashboardConfig(const std::string_view config_key, const std::string_view config_value) override {}
};
#endif //!TESTSQLDATABASE_H