From b0dab0380750dba93df8231e1e6c1e216d9bdc69 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Mon, 28 Sep 2026 23:19:41 -0500 Subject: [PATCH] chore(dashboard): remove the Maintenance page The page held one-off repairs from Nexus Dashboard (approve known pet names, find/delete orphaned pet names, remove every buff, fix property clone IDs, list mission rewards without a commendation price). Removed the page, its sidebar entry, its /api/maintenance/* routes, the `maintenance` permission and the two database calls only it used (GetAllPetNames, FixPropertyCloneIds). The scheduled tasks (lift expired bans, fill in pet owners, approve known pet names) and property model import/removal stay. Check: the Admin sidebar group has no Maintenance link; /maintenance is a 404; the Tasks page still lists "Approve known pet names" and "Fill in pet owners"; property import still works; the Permissions page no longer lists "Data maintenance". Co-Authored-By: Claude Opus 5.5 --- dCommon/Permissions.cpp | 1 - dDashboardServer/routes/DashboardRoutes.cpp | 1 - dDashboardServer/routes/MaintenanceRoutes.cpp | 136 ------------------ dDashboardServer/routes/MaintenanceRoutes.h | 2 +- dDashboardServer/templates/header.jinja2 | 5 +- dDashboardServer/templates/maintenance.jinja2 | 108 -------------- .../ITables/IDashboardMaintenance.h | 8 +- dDatabase/GameDatabase/MySQL/MySQLDatabase.h | 2 - .../GameDatabase/MySQL/Tables/Dashboard.cpp | 14 -- .../GameDatabase/SQLite/SQLiteDatabase.h | 2 - .../GameDatabase/SQLite/Tables/Dashboard.cpp | 17 --- .../GameDatabase/TestSQL/TestSQLDatabase.h | 2 - docs/Dashboard.md | 2 +- tests/dDatabaseTests/DatabaseParityTests.cpp | 2 - 14 files changed, 5 insertions(+), 297 deletions(-) delete mode 100644 dDashboardServer/templates/maintenance.jinja2 diff --git a/dCommon/Permissions.cpp b/dCommon/Permissions.cpp index 91f3daffa..9f8e21aab 100644 --- a/dCommon/Permissions.cpp +++ b/dCommon/Permissions.cpp @@ -102,7 +102,6 @@ namespace { { "events_manage", "Server", "Scheduled events: features", "Scheduled events that switch game features (event_1..event_8) on and off", 8 }, { "server_live_update", "Server", "Live updates", "Move every server and world instance onto a new build without a restart (players see a short loading screen), and cancel one", 9 }, { "instances_manage", "Server", "Instance limits", "Change players per instance and spare instances per zone (the master server applies them)", 9 }, - { "maintenance", "Server", "Data maintenance", "Repair tools on the Maintenance page", 9 }, { "backups", "Server", "Database backups", "Make, download and delete database backups (downloads also need your password)", 9 }, { "webhooks", "Server", "Webhooks", "Add and change outgoing alert webhooks", 9 }, { "email_settings", "Server", "Email settings", "Connect the mail account and send test emails", 9 }, diff --git a/dDashboardServer/routes/DashboardRoutes.cpp b/dDashboardServer/routes/DashboardRoutes.cpp index 43df741ad..90e94c191 100644 --- a/dDashboardServer/routes/DashboardRoutes.cpp +++ b/dDashboardServer/routes/DashboardRoutes.cpp @@ -443,7 +443,6 @@ void RegisterDashboardRoutes() { SimplePage("/pet_names", Perm("moderate_pet_names"), "pet_names.jinja2", "pet_names", "Pet names"); SimplePage("/send_mail", Perm("mail_send"), "send_mail.jinja2", "send_mail", "Send mail"); SimplePage("/api_docs", 0, "api_docs.jinja2", "api_docs", "API documentation"); - SimplePage("/maintenance", Perm("maintenance"), "maintenance.jinja2", "maintenance", "Data maintenance tools"); SimplePage("/webhooks", Perm("webhooks"), "webhooks.jinja2", "webhooks", "Outgoing webhooks for alerts"); SimplePage("/tasks", Perm("tasks_view"), "tasks.jinja2", "tasks", "Scheduled tasks: schedules, runs and logs"); Route(eHTTPMethod::GET, "/api/zones", Perm("characters_rescue"), "Every zone with its name, for pickers: {zones: [{id, name}]}", [](HTTPReply& reply, const HTTPContext&) { diff --git a/dDashboardServer/routes/MaintenanceRoutes.cpp b/dDashboardServer/routes/MaintenanceRoutes.cpp index aaf700a3f..8aa7f1775 100644 --- a/dDashboardServer/routes/MaintenanceRoutes.cpp +++ b/dDashboardServer/routes/MaintenanceRoutes.cpp @@ -1,8 +1,6 @@ #include "MaintenanceRoutes.h" -#include #include -#include #include #include "RouteUtils.h" @@ -10,10 +8,7 @@ #include "WSRoutes.h" #include "Background.h" #include "Scheduler.h" -#include "PlayerActions.h" -#include "ClientAssets.h" #include "Database.h" -#include "CDClientDatabase.h" #include "Game.h" #include "Logger.h" #include "eHTTPMethod.h" @@ -30,11 +25,6 @@ namespace { // Requests are capped at 3 MB by the web server, and JSON escaping makes the body bigger than the LXFML constexpr size_t MAX_IMPORT_BYTES = 2 * 1024 * 1024; - bool AnyoneOnline() { - const auto state = ServerState::GetServerStateJson(); - return state["stats"].value("onlinePlayers", 0u) > 0; - } - // Every pet in every inventory: pet id -> the character holding it std::map PetHolders(GameDatabase& db, size_t& characters) { std::map holders; @@ -103,132 +93,6 @@ void RegisterMaintenanceTasks() { } void RegisterMaintenanceRoutes() { - Route(eHTTPMethod::POST, "/api/maintenance/pet_names/auto_approve", Perm("maintenance"), "Approve pending pet names that were approved for another pet before", - [](HTTPReply& reply, const HTTPContext& context) { - const auto approved = Database::Get()->ApprovePreviouslyApprovedPetNames(); - Audit(context, "auto_approve_pet_names", std::to_string(approved) + " pet name(s)"); - if (approved) BroadcastTableChanged("pet_names"); - JsonSuccess(reply, { {"message", "Approved " + std::to_string(approved) + " pet name(s)"} }); - }); - - Route(eHTTPMethod::POST, "/api/maintenance/pet_names/orphans", Perm("maintenance"), - "Find (or with {delete: true}, remove) pet names whose pet no longer exists. Runs in the background: returns {requestId}", - [](HTTPReply& reply, const HTTPContext& context) { - const auto body = ParseBody(context); - const bool remove = body && body->value("delete", false); - const auto requestId = PlayerActions::Begin(context.accountId, std::chrono::minutes(30)); - const auto actor = context; - const bool queued = Background::Run("pet_name_orphans", [remove](GameDatabase& db) -> nlohmann::json { - size_t characters = 0; - const auto holders = PetHolders(db, characters); - - const auto names = db.GetAllPetNames(); - nlohmann::json orphans = nlohmann::json::array(); - for (const auto& [id, name] : names) { - if (!holders.contains(id)) orphans.push_back({ {"id", std::to_string(id)}, {"name", name} }); - } - nlohmann::json result{ - {"characters", characters}, {"petsFound", holders.size()}, {"petNames", names.size()}, - {"orphaned", orphans.size()}, {"sample", nlohmann::json(orphans.begin(), orphans.begin() + std::min(orphans.size(), 20))} - }; - if (!remove) return result; - // A mismatch in how ids are stored would make every name look orphaned; never delete in that case - if (!names.empty() && orphans.size() * 2 > names.size()) { - result["refused"] = "Refusing to delete: more than half of all pet names look orphaned, which suggests an ID mismatch rather than real orphans"; - return result; - } - for (const auto& orphan : orphans) db.RejectPetName(std::stoll(orphan["id"].get())); - result["deleted"] = orphans.size(); - return result; - }, [requestId, actor](nlohmann::json result, const std::string& error) { - if (!error.empty()) return PlayerActions::Finish(requestId, { false, "The scan failed: " + error }); - if (result.contains("refused")) return PlayerActions::Finish(requestId, { false, result["refused"].get(), result }); - if (result.contains("deleted")) { - Audit(actor, "delete_orphaned_pet_names", std::to_string(result["deleted"].get()) + " pet name(s)"); - BroadcastTableChanged("pet_names"); - } - PlayerActions::Finish(requestId, { true, std::to_string(result["orphaned"].get()) + " orphaned pet name(s)" + - (result.contains("deleted") ? ", deleted" : ""), result }); - }); - if (!queued) { - PlayerActions::Finish(requestId, { false, "This scan is already running" }); - return JsonError(reply, eHTTPStatusCode::CONFLICT, "This scan is already running"); - } - JsonSuccess(reply, { {"requestId", requestId} }); - }); - - Route(eHTTPMethod::POST, "/api/maintenance/remove_buffs", Perm("maintenance"), - "Remove every buff from every character (only while nobody is online). Body: {dry_run}. Runs in the background: returns {requestId}", - [](HTTPReply& reply, const HTTPContext& context) { - const auto body = ParseBody(context); - const bool dryRun = !body || body->value("dry_run", true); - if (!dryRun && AnyoneOnline()) return JsonError(reply, eHTTPStatusCode::CONFLICT, "Players are online; their worlds would overwrite this. Try again when nobody is online."); - - const auto requestId = PlayerActions::Begin(context.accountId, std::chrono::minutes(30)); - const auto actor = context; - const bool queued = Background::Run("remove_buffs", [dryRun](GameDatabase& db) -> nlohmann::json { - // First pass only records ids so no writes happen while the cursor is open - std::vector withBuffs; - db.ForEachCharacterXml([&](LWOOBJID id, const std::string& xml) { - if (xml.find("FirstChildElement("dest") : nullptr; - auto* buff = dest ? dest->FirstChildElement("buff") : nullptr; - if (!buff) continue; - dest->DeleteChild(buff); - tinyxml2::XMLPrinter printer(nullptr, true); - doc.Print(&printer); - db.UpdateCharacterXml(id, printer.CStr()); - changed++; - } - return { {"characters", changed}, {"dryRun", false} }; - }, [requestId, actor](nlohmann::json result, const std::string& error) { - if (!error.empty()) return PlayerActions::Finish(requestId, { false, "Failed: " + error }); - const auto count = result["characters"].get(); - if (result["dryRun"].get()) return PlayerActions::Finish(requestId, { true, std::to_string(count) + " character(s) have buffs", result }); - Audit(actor, "remove_all_buffs", std::to_string(count) + " character(s)"); - PlayerActions::Finish(requestId, { true, "Removed buffs from " + std::to_string(count) + " character(s)", result }); - }); - if (!queued) { - PlayerActions::Finish(requestId, { false, "This is already running" }); - return JsonError(reply, eHTTPStatusCode::CONFLICT, "This is already running"); - } - JsonSuccess(reply, { {"requestId", requestId} }); - }); - - Route(eHTTPMethod::POST, "/api/maintenance/fix_clone_ids", Perm("maintenance"), "Repair properties whose clone ID doesn't match their owner's", - [](HTTPReply& reply, const HTTPContext& context) { - const auto fixed = Database::Get()->FixPropertyCloneIds(); - Audit(context, "fix_property_clone_ids", std::to_string(fixed) + " propert(ies)"); - if (fixed) BroadcastTableChanged("properties"); - JsonSuccess(reply, { {"fixed", fixed}, {"message", "Fixed " + std::to_string(fixed) + " propert" + (fixed == 1 ? "y" : "ies")} }); - }); - - Route(eHTTPMethod::GET, "/api/maintenance/missing_commendation_items", Perm("maintenance"), "Mission reward items with no commendation vendor price (game data check)", - [](HTTPReply& reply, const HTTPContext& context) { - auto rows = CDClientDatabase::ExecuteQuery( - "SELECT DISTINCT r.lot FROM (" - "SELECT reward_item1 AS lot FROM Missions UNION SELECT reward_item2 FROM Missions " - "UNION SELECT reward_item3 FROM Missions UNION SELECT reward_item4 FROM Missions) r " - "LEFT JOIN ComponentsRegistry cr ON cr.id = r.lot AND cr.component_type = 11 " - "LEFT JOIN ItemComponent ic ON ic.id = cr.component_id " - "WHERE r.lot > 0 AND (ic.commendationLOT IS NULL OR ic.commendationCost IS NULL) ORDER BY r.lot;"); - nlohmann::json items = nlohmann::json::array(); - while (!rows.eof()) { - const LOT lot = rows.getIntField("lot"); - items.push_back({ {"lot", lot}, {"name", ClientAssets::ItemName(lot)} }); - rows.nextRow(); - } - JsonReply(reply, eHTTPStatusCode::OK, { {"items", items} }); - }); - Route(eHTTPMethod::POST, "/api/properties/:id/import", Perm("properties_import"), "Import LXFML models onto a property. Body: {lxfml, position: [x,y,z] (optional)}", [](HTTPReply& reply, const HTTPContext& context) { const auto propertyId = PathId(context.path, 2); diff --git a/dDashboardServer/routes/MaintenanceRoutes.h b/dDashboardServer/routes/MaintenanceRoutes.h index 13b9aa578..73fd8c64f 100644 --- a/dDashboardServer/routes/MaintenanceRoutes.h +++ b/dDashboardServer/routes/MaintenanceRoutes.h @@ -1,6 +1,6 @@ #pragma once -// Operator data repairs (maintenance page) and model import +// Property model import and removal void RegisterMaintenanceRoutes(); // Scheduled tasks (approving pet names that were approved before, filling in pet owners); before Scheduler::Initialize diff --git a/dDashboardServer/templates/header.jinja2 b/dDashboardServer/templates/header.jinja2 index 69cc0154d..e403032c0 100644 --- a/dDashboardServer/templates/header.jinja2 +++ b/dDashboardServer/templates/header.jinja2 @@ -118,8 +118,8 @@ {% endif %} - {% set adminPages = ["settings", "settings_history", "permissions", "tasks", "play_keys", "backups", "maintenance", "webhooks", "client_assets", "ugc"] %} - {% if can.settings or can.permissions_manage or can.tasks_view or can.play_keys_manage or can.backups or can.maintenance or can.webhooks or can.client_files or can.ugc_manage %} + {% set adminPages = ["settings", "settings_history", "permissions", "tasks", "play_keys", "backups", "webhooks", "client_assets", "ugc"] %} + {% if can.settings or can.permissions_manage or can.tasks_view or can.play_keys_manage or can.backups or can.webhooks or can.client_files or can.ugc_manage %} - - -
-
-
Buffs
-
-

Remove every buff from every character, e.g. after a buff got stuck. Only possible while nobody is online.

- - -

-			
-
-
-
-
-
Property clone IDs
-
-

Repair properties whose clone ID doesn't match their owner's (left behind by an old bug), which stops them loading.

- -

-			
-
-
-
-
-
Game data check
-
-

Mission reward items that have no commendation vendor price in the game data.

- -
-
-
-
- -{% endblock %} - -{% block scripts %} - -{% endblock %} diff --git a/dDatabase/GameDatabase/ITables/IDashboardMaintenance.h b/dDatabase/GameDatabase/ITables/IDashboardMaintenance.h index 0b5386a87..eecce3d4b 100644 --- a/dDatabase/GameDatabase/ITables/IDashboardMaintenance.h +++ b/dDatabase/GameDatabase/ITables/IDashboardMaintenance.h @@ -9,23 +9,17 @@ #include "dCommonVars.h" -// Bulk data repairs run from the dashboard's maintenance page +// Bulk data reads and repairs for dashboard reports and scheduled tasks class IDashboardMaintenance { public: // Approve pending pet names that an earlier pet with the same name was approved for. Returns rows changed. virtual uint32_t ApprovePreviouslyApprovedPetNames() = 0; - // Every pet name as (id, name) - virtual std::vector> GetAllPetNames() = 0; - // Call visit for every character's XML, one row at a time virtual void ForEachCharacterXml(const std::function& visit) = 0; // Same, but only characters whose XML contains `needle`; the database does the filtering virtual void ForEachCharacterXmlContaining(const std::string& needle, const std::function& visit) = 0; - - // Set each property's clone id to its owner's prop_clone_id where they differ. Returns rows changed. - virtual uint32_t FixPropertyCloneIds() = 0; }; #endif //!__IDASHBOARDMAINTENANCE__H__ diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index 2378a958e..94c02df3e 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -333,10 +333,8 @@ public: nlohmann::json GetCloneOwners(const std::vector& clones) override; nlohmann::json GetCloneVisitors(uint32_t zoneId, uint32_t cloneId, int64_t from, int64_t to, uint32_t limit) override; uint32_t ApprovePreviouslyApprovedPetNames() override; - std::vector> GetAllPetNames() override; void ForEachCharacterXml(const std::function& visit) override; void ForEachCharacterXmlContaining(const std::string& needle, const std::function& visit) override; - uint32_t FixPropertyCloneIds() override; std::optional GetAccountEmail(const uint32_t accountId) override; void SetAccountEmail(const uint32_t accountId, const std::string_view email, const bool confirmed) override; std::optional GetAccountIdByConfirmedEmail(const std::string_view email) override; diff --git a/dDatabase/GameDatabase/MySQL/Tables/Dashboard.cpp b/dDatabase/GameDatabase/MySQL/Tables/Dashboard.cpp index cc4817b5c..9ba3fec10 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Dashboard.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Dashboard.cpp @@ -339,13 +339,6 @@ uint32_t MySQLDatabase::ApprovePreviouslyApprovedPetNames() { "(SELECT name FROM (SELECT pet_name AS name FROM pet_names WHERE approved = 2) AS approved_names);")); } -std::vector> MySQLDatabase::GetAllPetNames() { - std::vector> pets; - auto result = ExecuteSelect("SELECT id, pet_name FROM pet_names;"); - while (result->next()) pets.emplace_back(result->getInt64("id"), std::string(result->getString("pet_name").c_str())); - return pets; -} - void MySQLDatabase::ForEachCharacterXml(const std::function& visit) { auto result = ExecuteSelect("SELECT id, xml_data FROM charxml;"); while (result->next()) visit(result->getInt64("id"), std::string(result->getString("xml_data").c_str())); @@ -355,10 +348,3 @@ void MySQLDatabase::ForEachCharacterXmlContaining(const std::string& needle, con auto result = ExecuteSelect("SELECT id, xml_data FROM charxml WHERE LOCATE(?, xml_data) > 0;", needle); while (result->next()) visit(result->getInt64("id"), std::string(result->getString("xml_data").c_str())); } - -uint32_t MySQLDatabase::FixPropertyCloneIds() { - return static_cast(ExecuteUpdate( - "UPDATE properties SET clone_id = (SELECT c.prop_clone_id FROM charinfo c WHERE c.id = properties.owner_id) " - "WHERE EXISTS (SELECT 1 FROM charinfo c WHERE c.id = properties.owner_id AND c.prop_clone_id IS NOT NULL " - "AND (properties.clone_id IS NULL OR c.prop_clone_id != properties.clone_id));")); -} diff --git a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h index 2a99e8191..650d33717 100644 --- a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h +++ b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h @@ -317,10 +317,8 @@ public: nlohmann::json GetCloneOwners(const std::vector& clones) override; nlohmann::json GetCloneVisitors(uint32_t zoneId, uint32_t cloneId, int64_t from, int64_t to, uint32_t limit) override; uint32_t ApprovePreviouslyApprovedPetNames() override; - std::vector> GetAllPetNames() override; void ForEachCharacterXml(const std::function& visit) override; void ForEachCharacterXmlContaining(const std::string& needle, const std::function& visit) override; - uint32_t FixPropertyCloneIds() override; std::optional GetAccountEmail(const uint32_t accountId) override; void SetAccountEmail(const uint32_t accountId, const std::string_view email, const bool confirmed) override; std::optional GetAccountIdByConfirmedEmail(const std::string_view email) override; diff --git a/dDatabase/GameDatabase/SQLite/Tables/Dashboard.cpp b/dDatabase/GameDatabase/SQLite/Tables/Dashboard.cpp index 058a31608..2e1a1fcce 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/Dashboard.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/Dashboard.cpp @@ -344,16 +344,6 @@ uint32_t SQLiteDatabase::ApprovePreviouslyApprovedPetNames() { "(SELECT name FROM (SELECT pet_name AS name FROM pet_names WHERE approved = 2) AS approved_names);")); } -std::vector> SQLiteDatabase::GetAllPetNames() { - std::vector> pets; - auto [_, result] = ExecuteSelect("SELECT id, pet_name FROM pet_names;"); - while (!result.eof()) { - pets.emplace_back(result.getInt64Field("id"), result.getStringField("pet_name")); - result.nextRow(); - } - return pets; -} - void SQLiteDatabase::ForEachCharacterXml(const std::function& visit) { auto [_, result] = ExecuteSelect("SELECT id, xml_data FROM charxml;"); while (!result.eof()) { @@ -369,10 +359,3 @@ void SQLiteDatabase::ForEachCharacterXmlContaining(const std::string& needle, co result.nextRow(); } } - -uint32_t SQLiteDatabase::FixPropertyCloneIds() { - return static_cast(ExecuteUpdate( - "UPDATE properties SET clone_id = (SELECT c.prop_clone_id FROM charinfo c WHERE c.id = properties.owner_id) " - "WHERE EXISTS (SELECT 1 FROM charinfo c WHERE c.id = properties.owner_id AND c.prop_clone_id IS NOT NULL " - "AND (properties.clone_id IS NULL OR c.prop_clone_id != properties.clone_id));")); -} diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h index 4e0bf7ef6..a963c84ed 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h @@ -367,10 +367,8 @@ class TestSQLDatabase : public GameDatabase { nlohmann::json GetCloneOwners(const std::vector& clones) override { return { {"owners", nlohmann::json::array()}, {"properties", nlohmann::json::array()} }; } nlohmann::json GetCloneVisitors(uint32_t zoneId, uint32_t cloneId, int64_t from, int64_t to, uint32_t limit) override { return nlohmann::json::array(); } uint32_t ApprovePreviouslyApprovedPetNames() override { return 0; }; - std::vector> GetAllPetNames() override { return {}; }; void ForEachCharacterXml(const std::function& visit) override {}; void ForEachCharacterXmlContaining(const std::string& needle, const std::function& visit) override {}; - uint32_t FixPropertyCloneIds() override { return 0; }; std::optional GetAccountEmail(const uint32_t accountId) override { return std::nullopt; }; void SetAccountEmail(const uint32_t accountId, const std::string_view email, const bool confirmed) override {}; std::optional GetAccountIdByConfirmedEmail(const std::string_view email) override { return std::nullopt; }; diff --git a/docs/Dashboard.md b/docs/Dashboard.md index 6c81705ea..a71deda59 100644 --- a/docs/Dashboard.md +++ b/docs/Dashboard.md @@ -62,7 +62,7 @@ What people can do depends on their account's GM level. Out of the box: | 4+ | Ban and lock accounts; email password reset links | | 5+ | Moderate pet names, properties and leaderboards; revoke strikes; change a character's missions; change the chat filter's words; send and schedule announcements | | 8+ | Manage accounts (create, change email or password, reset two-factor login) and GM levels; edit characters and replace their XML; give items back; read whispers and team chat; send chat into the game; attach items to mail, to one player or everyone; import models; shut down worlds; schedule restarts and events; run economy checks; play keys, client files and the vanity files; see scheduled tasks, the audit log, server logs, crash dumps, server health and instance load; the developer tools | -| 9 | Delete accounts; change scheduled tasks, instance limits, settings and permissions (these two are always GM 9 only), webhooks and email settings; backups; data maintenance | +| 9 | Delete accounts; change scheduled tasks, instance limits, settings and permissions (these two are always GM 9 only), webhooks and email settings; backups | Each of these is a named permission, and you can change the lowest GM level allowed for any of them without rebuilding or restarting: diff --git a/tests/dDatabaseTests/DatabaseParityTests.cpp b/tests/dDatabaseTests/DatabaseParityTests.cpp index 86763108b..3aa20fab2 100644 --- a/tests/dDatabaseTests/DatabaseParityTests.cpp +++ b/tests/dDatabaseTests/DatabaseParityTests.cpp @@ -626,7 +626,6 @@ TEST_F(ParitySeeded, PetNames) { Both("GetPetNamesTable order " + std::to_string(column), [&](GameDatabase& db) { return db.GetPetNamesTable(0, 10, "", column, false); }); } Both("GetPetsWithUnknownOwner", [](GameDatabase& db) { return db.GetPetsWithUnknownOwner(); }); - Both("GetAllPetNames", [](GameDatabase& db) { auto names = db.GetAllPetNames(); std::sort(names.begin(), names.end()); return names; }); Both("ApprovePreviouslyApprovedPetNames", [](GameDatabase& db) { return db.ApprovePreviouslyApprovedPetNames(); }); Both("SetPetOwner", [](GameDatabase& db) { db.SetPetOwner(PET3, 0); return json{ db.GetPetsWithUnknownOwner(), db.GetPetNameInfo(PET3) }; }); Both("ApprovePetName", [](GameDatabase& db) { db.ApprovePetName(PET3); return db.GetPetNameInfo(PET3); }); @@ -708,7 +707,6 @@ TEST_F(ParitySeeded, Properties) { Both("GetPropertiesOwnedBy", [](GameDatabase& db) { return db.GetPropertiesOwnedBy(CHAR_ALICE); }); Both("GetPropertyRecord", [](GameDatabase& db) { return json{ db.GetPropertyRecord(PROP1), db.GetPropertyRecord(1) }; }); Both("GetPropertyModelRecords", [](GameDatabase& db) { return db.GetPropertyModelRecords(PROP1); }); - Both("FixPropertyCloneIds", [](GameDatabase& db) { return db.FixPropertyCloneIds(); }); Both("GetDashboardSnapshot", [](GameDatabase& db) { return db.GetDashboardSnapshot(); }); }