mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
feat(db): chat log guild, team and filter result; chat flags
chat_log gets guild_id, team_id and filtered (migrations mysql 100, sqlite 83), and the chat log queries take a time range, a guild, a team, a conversation between two characters and paging back by id. Whispers are their own visibility switch (includeWhispers), apart from team and guild chat (includePrivate). Whisper partners and teams that talked are listed with counts. chat_flags, chat_flag_messages and chat_flag_events (mysql 101, sqlite 84) hold flagged chat with a copy of the messages around it, its status, note, linked player report and history. Open flags are in the dashboard snapshot. Tests on SQLite alone (dDatabaseSqliteTests) and in the MySQL parity tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -335,6 +335,18 @@ public:
|
||||
uint64_t InsertChatMessage(const ChatMessage& message) override;
|
||||
std::vector<ChatMessage> GetChatMessages(const ChatQuery& query) override;
|
||||
uint64_t CountChatMessages(const ChatQuery& query) override;
|
||||
std::vector<WhisperPartner> GetWhisperPartners(LWOOBJID characterId, uint32_t offset, uint32_t limit) override;
|
||||
uint64_t CountWhisperPartners(LWOOBJID characterId) override;
|
||||
std::vector<ChatTeam> GetChatTeams(LWOOBJID characterId, uint32_t offset, uint32_t limit) override;
|
||||
uint64_t CountChatTeams(LWOOBJID characterId) override;
|
||||
uint64_t InsertChatFlag(const ChatFlag& flag, const std::vector<uint64_t>& messageIds) override;
|
||||
std::optional<ChatFlag> GetChatFlag(uint64_t id) override;
|
||||
std::vector<ChatFlag> GetChatFlags(const ChatFlagQuery& query) override;
|
||||
uint64_t CountChatFlags(const ChatFlagQuery& query) override;
|
||||
bool UpdateChatFlag(uint64_t id, const std::string& status, const std::string& note, uint64_t playerReportId, int64_t time, const std::string& updatedBy) override;
|
||||
void InsertChatFlagEvent(const ChatFlagEvent& event) override;
|
||||
std::vector<ChatFlagEvent> GetChatFlagEvents(uint64_t flagId) override;
|
||||
std::vector<std::pair<uint64_t, uint64_t>> GetFlaggedMessages(const std::vector<uint64_t>& messageIds) override;
|
||||
void InsertModerationDecision(const std::string& kind, int64_t subjectId, const std::string& subject, bool approved, const std::string& reason, int64_t time) override;
|
||||
nlohmann::json GetModerationDecisions(const std::string& kind, int64_t subjectId, uint32_t limit) override;
|
||||
Totp GetTotp(uint32_t accountId) override;
|
||||
|
||||
@@ -28,6 +28,7 @@ set(DDATABASES_DATABASES_MYSQL_TABLES_SOURCES
|
||||
"ServerTraffic.cpp"
|
||||
"MessageCaptures.cpp"
|
||||
"ChatLog.cpp"
|
||||
"ChatFlags.cpp"
|
||||
"RelatedData.cpp"
|
||||
"ActivityLog.cpp"
|
||||
"Behaviors.cpp"
|
||||
|
||||
107
dDatabase/GameDatabase/MySQL/Tables/ChatFlags.cpp
Normal file
107
dDatabase/GameDatabase/MySQL/Tables/ChatFlags.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#include "MySQLDatabase.h"
|
||||
|
||||
namespace {
|
||||
// Every column but messages (the copy of the chat, only read for one flag)
|
||||
constexpr const char* LIST_COLUMNS = "id, created_at, created_by_id, created_by, status, channel, character_id, character_name, account_id, "
|
||||
"first_message_id, last_message_id, first_time, last_time, excerpt, note, player_report_id, updated_at, updated_by";
|
||||
|
||||
// Each filter binds the same parameters whether it is on or off (see ChatLog.cpp)
|
||||
std::string Where(const IChatFlags::ChatFlagQuery& q) {
|
||||
std::string where = " WHERE (channel IN ('zone', 'web') OR (? = 1 AND channel IN ('team', 'guild')) OR (? = 1 AND channel = 'whisper'))";
|
||||
where += q.status.empty() ? " AND (? = '' AND ? = '')" : " AND (? <> '' AND status = ?)";
|
||||
where += q.characterId == 0 ? " AND (? = 0 AND ? = 0)" : " AND (? <> 0 AND character_id = ?)";
|
||||
where += q.accountId == 0 ? " AND (? = 0 AND ? = 0)" : " AND (? <> 0 AND account_id = ?)";
|
||||
return where;
|
||||
}
|
||||
|
||||
IChatFlags::ChatFlag Row(sql::ResultSet& r, bool withMessages) {
|
||||
IChatFlags::ChatFlag f;
|
||||
f.id = r.getUInt64("id");
|
||||
f.createdAt = r.getInt64("created_at");
|
||||
f.createdById = r.getUInt("created_by_id");
|
||||
f.createdBy = r.getString("created_by").c_str();
|
||||
f.status = r.getString("status").c_str();
|
||||
f.channel = r.getString("channel").c_str();
|
||||
f.characterId = r.getInt64("character_id");
|
||||
f.characterName = r.getString("character_name").c_str();
|
||||
f.accountId = r.getUInt("account_id");
|
||||
f.firstMessageId = r.getUInt64("first_message_id");
|
||||
f.lastMessageId = r.getUInt64("last_message_id");
|
||||
f.firstTime = r.getInt64("first_time");
|
||||
f.lastTime = r.getInt64("last_time");
|
||||
f.excerpt = r.getString("excerpt").c_str();
|
||||
f.note = r.getString("note").c_str();
|
||||
if (withMessages) f.messages = r.getString("messages").c_str();
|
||||
f.playerReportId = r.getUInt64("player_report_id");
|
||||
f.updatedAt = r.getInt64("updated_at");
|
||||
f.updatedBy = r.getString("updated_by").c_str();
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
#define CHAT_FLAG_QUERY_PARAMS(q) (q).includePrivate, (q).includeWhispers, (q).status, (q).status, (q).characterId, (q).characterId, (q).accountId, (q).accountId
|
||||
|
||||
uint64_t MySQLDatabase::InsertChatFlag(const ChatFlag& f, const std::vector<uint64_t>& messageIds) {
|
||||
ExecuteInsert("INSERT INTO chat_flags (created_at, created_by_id, created_by, status, channel, character_id, character_name, account_id, first_message_id, "
|
||||
"last_message_id, first_time, last_time, excerpt, note, messages, player_report_id, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
f.createdAt, f.createdById, f.createdBy, f.status, f.channel, f.characterId, f.characterName, f.accountId, f.firstMessageId, f.lastMessageId,
|
||||
f.firstTime, f.lastTime, f.excerpt, f.note, f.messages, f.playerReportId, f.updatedAt, f.updatedBy);
|
||||
auto last = ExecuteSelect("SELECT LAST_INSERT_ID() AS id;");
|
||||
const uint64_t id = last->next() ? last->getUInt64("id") : 0;
|
||||
for (const auto messageId : messageIds) {
|
||||
ExecuteInsert("INSERT IGNORE INTO chat_flag_messages (flag_id, message_id) VALUES (?, ?);", id, messageId);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
std::optional<IChatFlags::ChatFlag> MySQLDatabase::GetChatFlag(const uint64_t id) {
|
||||
auto result = ExecuteSelect("SELECT * FROM chat_flags WHERE id = ?;", id);
|
||||
if (!result->next()) return std::nullopt;
|
||||
return Row(*result.m_resultSet, true);
|
||||
}
|
||||
|
||||
std::vector<IChatFlags::ChatFlag> MySQLDatabase::GetChatFlags(const ChatFlagQuery& q) {
|
||||
std::vector<ChatFlag> flags;
|
||||
auto result = ExecuteSelect(std::string("SELECT ") + LIST_COLUMNS + " FROM chat_flags" + Where(q) + " ORDER BY id DESC LIMIT ? OFFSET ?;",
|
||||
CHAT_FLAG_QUERY_PARAMS(q), q.limit, q.offset);
|
||||
while (result->next()) flags.push_back(Row(*result.m_resultSet, false));
|
||||
return flags;
|
||||
}
|
||||
|
||||
uint64_t MySQLDatabase::CountChatFlags(const ChatFlagQuery& q) {
|
||||
auto result = ExecuteSelect("SELECT COUNT(*) AS count FROM chat_flags" + Where(q) + ";", CHAT_FLAG_QUERY_PARAMS(q));
|
||||
return result->next() ? result->getUInt64("count") : 0;
|
||||
}
|
||||
|
||||
bool MySQLDatabase::UpdateChatFlag(const uint64_t id, const std::string& status, const std::string& note, const uint64_t playerReportId, const int64_t time, const std::string& updatedBy) {
|
||||
auto exists = ExecuteSelect("SELECT id FROM chat_flags WHERE id = ?;", id);
|
||||
if (!exists->next()) return false;
|
||||
ExecuteUpdate("UPDATE chat_flags SET status = ?, note = ?, player_report_id = ?, updated_at = ?, updated_by = ? WHERE id = ?;", status, note, playerReportId, time, updatedBy, id);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MySQLDatabase::InsertChatFlagEvent(const ChatFlagEvent& e) {
|
||||
ExecuteInsert("INSERT INTO chat_flag_events (flag_id, time, account_id, actor, action, detail) VALUES (?, ?, ?, ?, ?, ?);",
|
||||
e.flagId, e.time, e.accountId, e.actor, e.action, e.detail);
|
||||
}
|
||||
|
||||
std::vector<IChatFlags::ChatFlagEvent> MySQLDatabase::GetChatFlagEvents(const uint64_t flagId) {
|
||||
std::vector<ChatFlagEvent> events;
|
||||
auto result = ExecuteSelect("SELECT * FROM chat_flag_events WHERE flag_id = ? ORDER BY id ASC;", flagId);
|
||||
while (result->next()) {
|
||||
events.push_back({ result->getUInt64("id"), result->getUInt64("flag_id"), result->getInt64("time"), result->getUInt("account_id"),
|
||||
std::string(result->getString("actor").c_str()), std::string(result->getString("action").c_str()), std::string(result->getString("detail").c_str()) });
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint64_t, uint64_t>> MySQLDatabase::GetFlaggedMessages(const std::vector<uint64_t>& messageIds) {
|
||||
std::vector<std::pair<uint64_t, uint64_t>> flagged;
|
||||
if (messageIds.empty()) return flagged;
|
||||
// Numbers only, written by us: safe to put in the query
|
||||
std::string list;
|
||||
for (const auto id : messageIds) list += (list.empty() ? "" : ",") + std::to_string(id);
|
||||
auto result = ExecuteSelect("SELECT message_id, MAX(flag_id) AS flag_id FROM chat_flag_messages WHERE message_id IN (" + list + ") GROUP BY message_id ORDER BY message_id;");
|
||||
while (result->next()) flagged.emplace_back(result->getUInt64("message_id"), result->getUInt64("flag_id"));
|
||||
return flagged;
|
||||
}
|
||||
@@ -1,48 +1,123 @@
|
||||
#include "MySQLDatabase.h"
|
||||
#include "GeneralUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
// The filters are all optional. Each binds the same parameters whether it is on or off, so both queries below take one
|
||||
// argument list. A filter that is off only compares its own parameters (worked out once, not per row); one that is on is
|
||||
// a plain comparison, so the character and account filters are answered from chat_log's indexes instead of a full scan.
|
||||
// a plain comparison, so the character, account, guild and team filters are answered from chat_log's indexes instead of
|
||||
// a full scan.
|
||||
namespace {
|
||||
std::string Where(const IChatLog::ChatQuery& q) {
|
||||
std::string where = " WHERE id > ?";
|
||||
where += q.beforeId == 0 ? " AND (? = 0 AND ? = 0)" : " AND (? <> 0 AND id < ?)";
|
||||
where += q.channel.empty() ? " AND (? = '' AND ? = '')" : " AND (? <> '' AND channel = ?)";
|
||||
where += q.includePrivate ? " AND (? = 1)" : " AND (? = 1 OR channel IN ('zone', 'web'))";
|
||||
// Zone and web chat for everyone who may read chat; team and guild chat, and whispers, each behind their own permission
|
||||
where += " AND (channel IN ('zone', 'web') OR (? = 1 AND channel IN ('team', 'guild')) OR (? = 1 AND channel = 'whisper'))";
|
||||
where += q.characterId == 0 ? " AND (? = 0 AND ? = 0 AND ? = 0)" : " AND (? <> 0 AND (sender_id = ? OR recipient_id = ?))";
|
||||
where += q.otherCharacterId == 0 ? " AND (? = 0 AND ? = 0 AND ? = 0)" : " AND (? <> 0 AND (sender_id = ? OR recipient_id = ?))";
|
||||
where += q.accountId == 0 ? " AND (? = 0 AND ? = 0)" : " AND (? <> 0 AND account_id = ?)";
|
||||
where += q.zoneId == 0 ? " AND (? = 0 AND ? = 0)" : " AND (? <> 0 AND zone_id = ?)";
|
||||
where += q.instanceId < 0 ? " AND (? < 0 AND ? < 0)" : " AND (? >= 0 AND instance_id = ?)";
|
||||
where += q.since == 0 ? " AND (? = 0 AND ? = 0)" : " AND (? <> 0 AND time >= ?)";
|
||||
where += q.until == 0 ? " AND (? = 0 AND ? = 0)" : " AND (? <> 0 AND time < ?)";
|
||||
where += q.guildId == 0 ? " AND (? = 0 AND ? = 0)" : " AND (? <> 0 AND guild_id = ?)";
|
||||
where += q.teamId == 0 ? " AND (? = 0 AND ? = 0)" : " AND (? <> 0 AND team_id = ?)";
|
||||
where += q.search.empty() ? " AND (? = '' AND ? = '' AND ? = '' AND ? = '')"
|
||||
: " AND (? <> '' AND (message LIKE CONCAT('%', ?, '%') ESCAPE '!' OR sender_name LIKE CONCAT('%', ?, '%') ESCAPE '!' OR recipient_name LIKE CONCAT('%', ?, '%') ESCAPE '!'))";
|
||||
where += q.blockedOnly ? " AND (? <> 0 AND blocked = 1)" : " AND (? = 0)";
|
||||
return where;
|
||||
}
|
||||
|
||||
IChatLog::ChatMessage Row(sql::ResultSet& result) {
|
||||
IChatLog::ChatMessage m{ result.getUInt64("id"), result.getInt64("time"), std::string(result.getString("channel").c_str()),
|
||||
result.getInt64("sender_id"), std::string(result.getString("sender_name").c_str()), result.getUInt("account_id"),
|
||||
result.getInt64("recipient_id"), std::string(result.getString("recipient_name").c_str()), result.getUInt("zone_id"),
|
||||
result.getUInt("instance_id"), result.getUInt("clone_id"), std::string(result.getString("message").c_str()),
|
||||
result.getInt("blocked") != 0 };
|
||||
m.guildId = result.getInt64("guild_id");
|
||||
m.teamId = result.getInt64("team_id");
|
||||
m.filtered = result.getInt("filtered") != 0;
|
||||
return m;
|
||||
}
|
||||
|
||||
// GROUP_CONCAT lists names in whatever order the database likes; sorted, so both databases agree
|
||||
std::string SortedNames(const std::string& concatenated) {
|
||||
auto names = GeneralUtils::SplitString(concatenated, ',');
|
||||
std::ranges::sort(names);
|
||||
std::string out;
|
||||
for (const auto& name : names) {
|
||||
if (name.empty()) continue;
|
||||
out += (out.empty() ? "" : ",") + name;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
// Every query ending in Where(q) takes the filter's parameters in this order, then its own
|
||||
#define CHAT_QUERY_PARAMS(q, search) static_cast<int64_t>((q).afterId), static_cast<int64_t>((q).beforeId), static_cast<int64_t>((q).beforeId), (q).channel, (q).channel, \
|
||||
(q).includePrivate, (q).includeWhispers, (q).characterId, (q).characterId, (q).characterId, (q).otherCharacterId, (q).otherCharacterId, (q).otherCharacterId, \
|
||||
(q).accountId, (q).accountId, (q).zoneId, (q).zoneId, (q).instanceId, (q).instanceId, (q).since, (q).since, (q).until, (q).until, (q).guildId, (q).guildId, \
|
||||
(q).teamId, (q).teamId, (q).search, search, search, search, (q).blockedOnly
|
||||
|
||||
uint64_t MySQLDatabase::InsertChatMessage(const ChatMessage& m) {
|
||||
ExecuteInsert("INSERT INTO chat_log (time, channel, sender_id, sender_name, account_id, recipient_id, recipient_name, zone_id, instance_id, clone_id, message, blocked) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
m.time, m.channel, m.senderId, m.senderName, m.accountId, m.recipientId, m.recipientName, m.zoneId, m.instanceId, m.cloneId, m.message, m.blocked);
|
||||
ExecuteInsert("INSERT INTO chat_log (time, channel, sender_id, sender_name, account_id, recipient_id, recipient_name, zone_id, instance_id, clone_id, message, blocked, guild_id, team_id, filtered) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
m.time, m.channel, m.senderId, m.senderName, m.accountId, m.recipientId, m.recipientName, m.zoneId, m.instanceId, m.cloneId, m.message, m.blocked,
|
||||
m.guildId, m.teamId, m.filtered || m.blocked);
|
||||
auto last = ExecuteSelect("SELECT LAST_INSERT_ID() AS id;"); // this connection's insert, not another server's
|
||||
return last->next() ? last->getUInt64("id") : 0;
|
||||
}
|
||||
|
||||
std::vector<IChatLog::ChatMessage> MySQLDatabase::GetChatMessages(const ChatQuery& q) {
|
||||
std::vector<ChatMessage> messages;
|
||||
auto result = ExecuteSelect("SELECT * FROM chat_log" + Where(q) + " ORDER BY id " + std::string(q.newestFirst ? "DESC" : "ASC") + " LIMIT ? OFFSET ?;", static_cast<int64_t>(q.afterId), q.channel, q.channel, q.includePrivate, q.characterId, q.characterId, q.characterId, q.accountId, q.accountId, q.zoneId, q.zoneId, q.instanceId, q.instanceId, q.since, q.since, q.search, GeneralUtils::LikeEscape(q.search, '!'), GeneralUtils::LikeEscape(q.search, '!'), GeneralUtils::LikeEscape(q.search, '!'), q.blockedOnly, q.limit, q.offset);
|
||||
while (result->next()) {
|
||||
messages.push_back({ result->getUInt64("id"), result->getInt64("time"), std::string(result->getString("channel").c_str()),
|
||||
result->getInt64("sender_id"), std::string(result->getString("sender_name").c_str()), result->getUInt("account_id"),
|
||||
result->getInt64("recipient_id"), std::string(result->getString("recipient_name").c_str()), result->getUInt("zone_id"),
|
||||
result->getUInt("instance_id"), result->getUInt("clone_id"), std::string(result->getString("message").c_str()),
|
||||
result->getInt("blocked") != 0 });
|
||||
}
|
||||
const auto search = GeneralUtils::LikeEscape(q.search, '!');
|
||||
auto result = ExecuteSelect("SELECT * FROM chat_log" + Where(q) + " ORDER BY id " + std::string(q.newestFirst ? "DESC" : "ASC") + " LIMIT ? OFFSET ?;",
|
||||
CHAT_QUERY_PARAMS(q, search), q.limit, q.offset);
|
||||
while (result->next()) messages.push_back(Row(*result.m_resultSet));
|
||||
return messages;
|
||||
}
|
||||
|
||||
uint64_t MySQLDatabase::CountChatMessages(const ChatQuery& q) {
|
||||
auto result = ExecuteSelect("SELECT COUNT(*) AS count FROM chat_log" + Where(q) + ";", static_cast<int64_t>(q.afterId), q.channel, q.channel, q.includePrivate, q.characterId, q.characterId, q.characterId, q.accountId, q.accountId, q.zoneId, q.zoneId, q.instanceId, q.instanceId, q.since, q.since, q.search, GeneralUtils::LikeEscape(q.search, '!'), GeneralUtils::LikeEscape(q.search, '!'), GeneralUtils::LikeEscape(q.search, '!'), q.blockedOnly);
|
||||
const auto search = GeneralUtils::LikeEscape(q.search, '!');
|
||||
auto result = ExecuteSelect("SELECT COUNT(*) AS count FROM chat_log" + Where(q) + ";", CHAT_QUERY_PARAMS(q, search));
|
||||
return result->next() ? result->getUInt64("count") : 0;
|
||||
}
|
||||
|
||||
std::vector<IChatLog::WhisperPartner> MySQLDatabase::GetWhisperPartners(const LWOOBJID characterId, const uint32_t offset, const uint32_t limit) {
|
||||
std::vector<WhisperPartner> partners;
|
||||
auto result = ExecuteSelect(
|
||||
"SELECT g.partner, g.messages, g.first_time, g.last_time, CASE WHEN l.sender_id = g.partner THEN l.sender_name ELSE l.recipient_name END AS name FROM "
|
||||
"(SELECT CASE WHEN sender_id = ? THEN recipient_id ELSE sender_id END AS partner, COUNT(*) AS messages, MIN(time) AS first_time, MAX(time) AS last_time, MAX(id) AS last_id "
|
||||
"FROM chat_log WHERE channel = 'whisper' AND (sender_id = ? OR recipient_id = ?) GROUP BY partner) g "
|
||||
"JOIN chat_log l ON l.id = g.last_id ORDER BY g.last_id DESC LIMIT ? OFFSET ?;", characterId, characterId, characterId, limit, offset);
|
||||
while (result->next()) {
|
||||
partners.push_back({ result->getInt64("partner"), std::string(result->getString("name").c_str()), result->getUInt64("messages"),
|
||||
result->getInt64("first_time"), result->getInt64("last_time") });
|
||||
}
|
||||
return partners;
|
||||
}
|
||||
|
||||
uint64_t MySQLDatabase::CountWhisperPartners(const LWOOBJID characterId) {
|
||||
auto result = ExecuteSelect("SELECT COUNT(DISTINCT CASE WHEN sender_id = ? THEN recipient_id ELSE sender_id END) AS count FROM chat_log "
|
||||
"WHERE channel = 'whisper' AND (sender_id = ? OR recipient_id = ?);", characterId, characterId, characterId);
|
||||
return result->next() ? result->getUInt64("count") : 0;
|
||||
}
|
||||
|
||||
std::vector<IChatLog::ChatTeam> MySQLDatabase::GetChatTeams(const LWOOBJID characterId, const uint32_t offset, const uint32_t limit) {
|
||||
std::vector<ChatTeam> teams;
|
||||
auto result = ExecuteSelect("SELECT team_id, COUNT(*) AS messages, MIN(time) AS first_time, MAX(time) AS last_time, MAX(id) AS last_id, GROUP_CONCAT(DISTINCT sender_name) AS senders "
|
||||
"FROM chat_log WHERE channel = 'team' AND team_id <> 0 AND (? = 0 OR team_id IN (SELECT team_id FROM chat_log WHERE channel = 'team' AND sender_id = ?)) "
|
||||
"GROUP BY team_id ORDER BY last_id DESC LIMIT ? OFFSET ?;", characterId, characterId, limit, offset);
|
||||
while (result->next()) {
|
||||
teams.push_back({ result->getInt64("team_id"), result->getUInt64("messages"), result->getInt64("first_time"), result->getInt64("last_time"),
|
||||
SortedNames(std::string(result->getString("senders").c_str())) });
|
||||
}
|
||||
return teams;
|
||||
}
|
||||
|
||||
uint64_t MySQLDatabase::CountChatTeams(const LWOOBJID characterId) {
|
||||
auto result = ExecuteSelect("SELECT COUNT(DISTINCT team_id) AS count FROM chat_log WHERE channel = 'team' AND team_id <> 0 "
|
||||
"AND (? = 0 OR team_id IN (SELECT team_id FROM chat_log WHERE channel = 'team' AND sender_id = ?));", characterId, characterId);
|
||||
return result->next() ? result->getUInt64("count") : 0;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,10 @@ namespace {
|
||||
"(SELECT COALESCE(MAX(id), 0) FROM audit_log) AS audit_log_max_id, "
|
||||
"(SELECT COALESCE(MAX(id), 0) FROM mail) AS mail_max_id, "
|
||||
"(SELECT COUNT(*) FROM economy_flags WHERE status = 0) AS open_economy_flags, "
|
||||
"(SELECT COALESCE(MAX(id), 0) FROM economy_flags) AS economy_flags_max_id;";
|
||||
"(SELECT COALESCE(MAX(id), 0) FROM economy_flags) AS economy_flags_max_id, "
|
||||
"(SELECT COUNT(*) FROM chat_flags WHERE status = 'open') AS open_chat_flags, "
|
||||
"(SELECT COALESCE(MAX(id), 0) FROM chat_flags) AS chat_flags_max_id, "
|
||||
"(SELECT COALESCE(MAX(updated_at), 0) FROM chat_flags) AS chat_flags_updated_at;";
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -264,6 +267,9 @@ IDashboardStats::Snapshot MySQLDatabase::GetDashboardSnapshot() {
|
||||
snapshot.mailMaxId = result->getUInt64("mail_max_id");
|
||||
snapshot.openEconomyFlags = result->getUInt64("open_economy_flags");
|
||||
snapshot.economyFlagsMaxId = result->getUInt64("economy_flags_max_id");
|
||||
snapshot.openChatFlags = result->getUInt64("open_chat_flags");
|
||||
snapshot.chatFlagsMaxId = result->getUInt64("chat_flags_max_id");
|
||||
snapshot.chatFlagsUpdatedAt = result->getUInt64("chat_flags_updated_at");
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user