feat(mail): mail history query for the dashboard

IMail::GetMailHistory and CountMailHistory return mail rows as stored,
deleted mail included, newest first, with the sender's and receiver's
accounts. Filters: character (sent or received), account, text in the
subject, body or a name (LIKE wildcards matched literally), state
(unread, read, attachment waiting, claimed, deleted) and whether to
include deleted mail. MySQL and SQLite share the SQL (MailSql.h).

Check: parity test ParitySeeded.Mail (GetMailHistory).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-29 00:41:44 -05:00
parent acb0e03a74
commit 180585f9c6
8 changed files with 196 additions and 0 deletions

View File

@@ -1,4 +1,6 @@
#include "MySQLDatabase.h"
#include "GeneralUtils.h"
#include "MailSql.h"
void MySQLDatabase::InsertNewMail(const MailInfo& mail) {
@@ -90,3 +92,26 @@ void MySQLDatabase::DeleteMail(const uint64_t mailId) {
// Kept for staff (the dashboard shows it as deleted); every read for the game skips it
ExecuteUpdate("UPDATE mail SET deleted_at=? WHERE id=? AND deleted_at=0 LIMIT 1;", static_cast<int64_t>(time(NULL)), mailId);
}
std::vector<IMail::MailRecord> MySQLDatabase::GetMailHistory(const MailQuery& q) {
const auto pattern = GeneralUtils::LikeEscape(q.search, '!');
auto res = ExecuteSelect(MailSql::Select() + MailSql::Where(q, "CONCAT('%', ?, '%')") + " ORDER BY m.id DESC LIMIT ? OFFSET ?;",
q.characterId, q.characterId, q.characterId, q.accountId, q.accountId, q.accountId, q.search, pattern, pattern, pattern, pattern, q.limit, q.offset);
std::vector<MailRecord> rows;
while (res->next()) {
rows.push_back({ res->getUInt64("id"), res->getInt64("sender_id"), std::string(res->getString("sender_name").c_str()), res->getUInt("sender_account"),
res->getInt64("receiver_id"), std::string(res->getString("receiver_name").c_str()), res->getUInt("receiver_account"), res->getInt64("time_sent"),
std::string(res->getString("subject").c_str()), std::string(res->getString("body").c_str()), res->getInt64("attachment_id"),
res->getInt("attachment_lot"), res->getInt64("attachment_subkey"), res->getInt("attachment_count"),
res->isNull("attachment_config") ? "" : std::string(res->getString("attachment_config").c_str()), res->getBoolean("was_read"),
res->getInt64("deleted_at") });
}
return rows;
}
uint64_t MySQLDatabase::CountMailHistory(const MailQuery& q) {
const auto pattern = GeneralUtils::LikeEscape(q.search, '!');
auto res = ExecuteSelect("SELECT COUNT(*) AS count FROM mail AS m" + MailSql::Where(q, "CONCAT('%', ?, '%')") + ";",
q.characterId, q.characterId, q.characterId, q.accountId, q.accountId, q.accountId, q.search, pattern, pattern, pattern, pattern);
return res->next() ? res->getUInt64("count") : 0;
}