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

@@ -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()
}