mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-05-14 03:15:05 +00:00
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:
1344
dDashboardServer/blueprints/ApiBlueprint.cpp
Normal file
1344
dDashboardServer/blueprints/ApiBlueprint.cpp
Normal file
File diff suppressed because it is too large
Load Diff
17
dDashboardServer/blueprints/ApiBlueprint.h
Normal file
17
dDashboardServer/blueprints/ApiBlueprint.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "crow.h"
|
||||
#include "crow/middlewares/session.h"
|
||||
|
||||
namespace ApiBlueprint {
|
||||
|
||||
using Session = crow::SessionMiddleware<crow::InMemoryStore>;
|
||||
using DashboardApp = crow::App<crow::CookieParser, Session>;
|
||||
|
||||
/**
|
||||
* Setup API routes
|
||||
* Registers all API endpoints for stats, accounts, and moderation
|
||||
*/
|
||||
void Setup(DashboardApp& app);
|
||||
|
||||
} // namespace ApiBlueprint
|
||||
129
dDashboardServer/blueprints/AuthBlueprint.cpp
Normal file
129
dDashboardServer/blueprints/AuthBlueprint.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "AuthBlueprint.h"
|
||||
#include "Database.h"
|
||||
#include <bcrypt/BCrypt.hpp>
|
||||
|
||||
namespace AuthBlueprint {
|
||||
|
||||
void Setup(DashboardApp& app) {
|
||||
// Login route
|
||||
CROW_ROUTE(app, "/api/login")
|
||||
.methods("POST"_method)
|
||||
([&](crow::request& req, crow::response& res) {
|
||||
auto body = crow::json::load(req.body);
|
||||
if (!body) {
|
||||
res.code = 400;
|
||||
res.set_header("Content-Type", "application/json");
|
||||
res.write("{\"error\": \"Invalid JSON\"}");
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string username = body["username"].s();
|
||||
std::string password = body["password"].s();
|
||||
|
||||
if (username.empty() || password.empty()) {
|
||||
res.code = 400;
|
||||
res.set_header("Content-Type", "application/json");
|
||||
res.write("{\"error\": \"Username and password required\"}");
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get account info from database
|
||||
auto accountInfo = Database::Get()->GetAccountInfo(username);
|
||||
if (!accountInfo) {
|
||||
res.code = 401;
|
||||
res.set_header("Content-Type", "application/json");
|
||||
res.write("{\"error\": \"Invalid credentials\"}");
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify password using bcrypt
|
||||
if (!BCrypt::validatePassword(password, accountInfo->bcryptPassword)) {
|
||||
res.code = 401;
|
||||
res.set_header("Content-Type", "application/json");
|
||||
res.write("{\"error\": \"Invalid credentials\"}");
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if account is banned or locked
|
||||
if (accountInfo->banned) {
|
||||
res.code = 403;
|
||||
res.set_header("Content-Type", "application/json");
|
||||
res.write("{\"error\": \"Account is banned\"}");
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (accountInfo->locked) {
|
||||
res.code = 403;
|
||||
res.set_header("Content-Type", "application/json");
|
||||
res.write("{\"error\": \"Account is locked\"}");
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create session
|
||||
auto& session = app.get_context<Session>(req);
|
||||
session.set("username", username);
|
||||
session.set("account_id", static_cast<int>(accountInfo->id));
|
||||
session.set("gm_level", static_cast<int>(accountInfo->maxGmLevel));
|
||||
|
||||
// Return success with user info
|
||||
crow::json::wvalue response;
|
||||
response["success"] = true;
|
||||
response["username"] = username;
|
||||
response["account_id"] = accountInfo->id;
|
||||
response["gm_level"] = static_cast<uint8_t>(accountInfo->maxGmLevel);
|
||||
|
||||
res.set_header("Content-Type", "application/json");
|
||||
res.write(response.dump());
|
||||
res.end();
|
||||
});
|
||||
|
||||
// Logout route
|
||||
CROW_ROUTE(app, "/api/logout")
|
||||
.methods("POST"_method)
|
||||
([&](crow::request& req, crow::response& res) {
|
||||
auto& session = app.get_context<Session>(req);
|
||||
|
||||
// Clear session
|
||||
session.remove("username");
|
||||
session.remove("account_id");
|
||||
session.remove("gm_level");
|
||||
|
||||
crow::json::wvalue response;
|
||||
response["success"] = true;
|
||||
|
||||
res.set_header("Content-Type", "application/json");
|
||||
res.write(response.dump());
|
||||
res.end();
|
||||
});
|
||||
|
||||
// Auth status route
|
||||
CROW_ROUTE(app, "/api/auth/status")
|
||||
([&](const crow::request& req) {
|
||||
auto& session = app.get_context<Session>(const_cast<crow::request&>(req));
|
||||
std::string username = session.template get<std::string>("username");
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
if (!username.empty()) {
|
||||
int account_id = session.template get<int>("account_id", -1);
|
||||
int gm_level = session.template get<int>("gm_level", -1);
|
||||
|
||||
response["authenticated"] = true;
|
||||
response["username"] = username;
|
||||
response["account_id"] = account_id;
|
||||
response["gm_level"] = gm_level;
|
||||
} else {
|
||||
response["authenticated"] = false;
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace AuthBlueprint
|
||||
17
dDashboardServer/blueprints/AuthBlueprint.h
Normal file
17
dDashboardServer/blueprints/AuthBlueprint.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "crow.h"
|
||||
#include "crow/middlewares/session.h"
|
||||
|
||||
namespace AuthBlueprint {
|
||||
|
||||
using Session = crow::SessionMiddleware<crow::InMemoryStore>;
|
||||
using DashboardApp = crow::App<crow::CookieParser, Session>;
|
||||
|
||||
/**
|
||||
* Setup authentication routes
|
||||
* Registers login, logout, and auth status endpoints
|
||||
*/
|
||||
void Setup(DashboardApp& app);
|
||||
|
||||
} // namespace AuthBlueprint
|
||||
234
dDashboardServer/blueprints/BugReportsBlueprint.cpp
Normal file
234
dDashboardServer/blueprints/BugReportsBlueprint.cpp
Normal file
@@ -0,0 +1,234 @@
|
||||
#include "BugReportsBlueprint.h"
|
||||
#include "Database.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "Logger.h"
|
||||
#include <ctime>
|
||||
|
||||
namespace BugReportsBlueprint {
|
||||
|
||||
// Helper function to get current user's account info from session
|
||||
std::optional<IAccounts::Info> GetCurrentUser(const crow::request& req, DashboardApp& app) {
|
||||
auto& session = app.get_context<Session>(const_cast<crow::request&>(req));
|
||||
std::string username = session.template get<std::string>("username");
|
||||
|
||||
if (username.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return Database::Get()->GetAccountInfo(username);
|
||||
}
|
||||
|
||||
// Helper function to get user's GM level
|
||||
eGameMasterLevel GetUserGMLevel(const crow::request& req, DashboardApp& app) {
|
||||
auto user = GetCurrentUser(req, app);
|
||||
if (!user) {
|
||||
return eGameMasterLevel::CIVILIAN;
|
||||
}
|
||||
return user->maxGmLevel;
|
||||
}
|
||||
|
||||
// Helper function to check if user has minimum GM level
|
||||
bool HasMinimumGMLevel(const crow::request& req, DashboardApp& app, eGameMasterLevel required) {
|
||||
auto level = GetUserGMLevel(req, app);
|
||||
return static_cast<uint8_t>(level) >= static_cast<uint8_t>(required);
|
||||
}
|
||||
|
||||
void Setup(DashboardApp& app) {
|
||||
// Get all bug reports (filtered by status)
|
||||
CROW_ROUTE(app, "/api/bugreports")
|
||||
.methods("GET"_method)
|
||||
([&](const crow::request& req) {
|
||||
// Anyone authenticated can view their own bug reports
|
||||
// GMs can view all
|
||||
auto user = GetCurrentUser(req, app);
|
||||
if (!user) {
|
||||
return crow::response(401, "{\"error\": \"Not authenticated\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
crow::json::wvalue::list data;
|
||||
|
||||
try {
|
||||
auto statusParam = req.url_params.get("status");
|
||||
std::string status = statusParam ? statusParam : "all";
|
||||
|
||||
std::vector<IBugReports::DetailedInfo> reports;
|
||||
|
||||
if (status == "resolved") {
|
||||
reports = Database::Get()->GetResolvedBugReports();
|
||||
} else if (status == "unresolved") {
|
||||
reports = Database::Get()->GetUnresolvedBugReports();
|
||||
} else {
|
||||
reports = Database::Get()->GetAllBugReports();
|
||||
}
|
||||
|
||||
bool isGM = static_cast<uint8_t>(user->maxGmLevel) >= static_cast<uint8_t>(eGameMasterLevel::MODERATOR);
|
||||
|
||||
for (const auto& report : reports) {
|
||||
// If not a GM, only show reports from user's own characters
|
||||
if (!isGM) {
|
||||
auto charInfo = Database::Get()->GetCharacterInfo(report.characterId);
|
||||
if (!charInfo || charInfo->accountId != user->id) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
crow::json::wvalue item;
|
||||
item["id"] = report.id;
|
||||
item["body"] = report.body;
|
||||
item["client_version"] = report.clientVersion;
|
||||
item["other_player"] = report.otherPlayer;
|
||||
item["selection"] = report.selection;
|
||||
item["character_id"] = static_cast<uint64_t>(report.characterId);
|
||||
item["submitted"] = report.submitted;
|
||||
item["resolved_time"] = report.resolved_time;
|
||||
item["resolved_by_id"] = report.resolved_by_id;
|
||||
item["resolution"] = report.resolution;
|
||||
|
||||
// Get character name
|
||||
auto charInfo = Database::Get()->GetCharacterInfo(report.characterId);
|
||||
if (charInfo) {
|
||||
item["character_name"] = charInfo->name;
|
||||
} else {
|
||||
item["character_name"] = "Unknown";
|
||||
}
|
||||
|
||||
data.push_back(std::move(item));
|
||||
}
|
||||
|
||||
response["data"] = std::move(data);
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["error"] = ex.what();
|
||||
return crow::response(500, response);
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Get a single bug report by ID
|
||||
CROW_ROUTE(app, "/api/bugreports/<uint>")
|
||||
.methods("GET"_method)
|
||||
([&](const crow::request& req, uint64_t report_id) {
|
||||
auto user = GetCurrentUser(req, app);
|
||||
if (!user) {
|
||||
return crow::response(401, "{\"error\": \"Not authenticated\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
auto report = Database::Get()->GetBugReportById(report_id);
|
||||
if (!report) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Bug report not found";
|
||||
return crow::response(404, response);
|
||||
}
|
||||
|
||||
// Check access rights
|
||||
bool canAccess = false;
|
||||
if (static_cast<uint8_t>(user->maxGmLevel) >= static_cast<uint8_t>(eGameMasterLevel::MODERATOR)) {
|
||||
canAccess = true;
|
||||
} else {
|
||||
auto charInfo = Database::Get()->GetCharacterInfo(report->characterId);
|
||||
if (charInfo && charInfo->accountId == user->id) {
|
||||
canAccess = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!canAccess) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Access denied";
|
||||
return crow::response(403, response);
|
||||
}
|
||||
|
||||
response["success"] = true;
|
||||
response["id"] = report->id;
|
||||
response["body"] = report->body;
|
||||
response["client_version"] = report->clientVersion;
|
||||
response["other_player"] = report->otherPlayer;
|
||||
response["selection"] = report->selection;
|
||||
response["character_id"] = static_cast<uint64_t>(report->characterId);
|
||||
response["submitted"] = report->submitted;
|
||||
response["resolved_time"] = report->resolved_time;
|
||||
response["resolved_by_id"] = report->resolved_by_id;
|
||||
response["resolution"] = report->resolution;
|
||||
|
||||
// Get character name
|
||||
auto charInfo = Database::Get()->GetCharacterInfo(report->characterId);
|
||||
if (charInfo) {
|
||||
response["character_name"] = charInfo->name;
|
||||
}
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Resolve a bug report
|
||||
CROW_ROUTE(app, "/api/bugreports/<uint>/resolve")
|
||||
.methods("POST"_method)
|
||||
([&](const crow::request& req, uint64_t report_id) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
auto body = crow::json::load(req.body);
|
||||
if (!body) {
|
||||
return crow::response(400, "{\"error\": \"Invalid JSON\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
auto user = GetCurrentUser(req, app);
|
||||
if (!user) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Not authenticated";
|
||||
return crow::response(401, response);
|
||||
}
|
||||
|
||||
std::string resolution;
|
||||
if (body.has("resolution"))
|
||||
resolution = std::string(body["resolution"].s());
|
||||
else
|
||||
resolution = "";
|
||||
|
||||
if (resolution.empty()) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Resolution message is required";
|
||||
return crow::response(response);
|
||||
}
|
||||
|
||||
// Check if report exists and is not already resolved
|
||||
auto report = Database::Get()->GetBugReportById(report_id);
|
||||
if (!report) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Bug report not found";
|
||||
return crow::response(404, response);
|
||||
}
|
||||
|
||||
if (report->resolved_time > 0) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Bug report already resolved";
|
||||
return crow::response(response);
|
||||
}
|
||||
|
||||
Database::Get()->ResolveBugReport(report_id, user->id, resolution);
|
||||
|
||||
response["success"] = true;
|
||||
response["message"] = "Bug report resolved successfully";
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace BugReportsBlueprint
|
||||
20
dDashboardServer/blueprints/BugReportsBlueprint.h
Normal file
20
dDashboardServer/blueprints/BugReportsBlueprint.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef __BUGREPORTSBLUEPRINT_H__
|
||||
#define __BUGREPORTSBLUEPRINT_H__
|
||||
|
||||
#include "crow.h"
|
||||
#include "crow/middlewares/session.h"
|
||||
|
||||
namespace BugReportsBlueprint {
|
||||
|
||||
using Session = crow::SessionMiddleware<crow::InMemoryStore>;
|
||||
using DashboardApp = crow::App<crow::CookieParser, Session>;
|
||||
|
||||
/**
|
||||
* Setup bug reports management routes
|
||||
* Registers routes for viewing and resolving bug reports
|
||||
*/
|
||||
void Setup(DashboardApp& app);
|
||||
|
||||
} // namespace BugReportsBlueprint
|
||||
|
||||
#endif // __BUGREPORTSBLUEPRINT_H__
|
||||
14
dDashboardServer/blueprints/CMakeLists.txt
Normal file
14
dDashboardServer/blueprints/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
set(DDASHBOARDSERVER_BLUEPRINTS
|
||||
"AuthBlueprint.cpp"
|
||||
"ApiBlueprint.cpp"
|
||||
"PageBlueprint.cpp"
|
||||
"PlayKeysBlueprint.cpp"
|
||||
"CharactersBlueprint.cpp"
|
||||
"MailBlueprint.cpp"
|
||||
"BugReportsBlueprint.cpp"
|
||||
"ModerationBlueprint.cpp"
|
||||
)
|
||||
|
||||
foreach(file ${DDASHBOARDSERVER_BLUEPRINTS})
|
||||
set(DDASHBOARDSERVER_BLUEPRINTS_SOURCES ${DDASHBOARDSERVER_BLUEPRINTS_SOURCES} "blueprints/${file}" PARENT_SCOPE)
|
||||
endforeach()
|
||||
263
dDashboardServer/blueprints/CharactersBlueprint.cpp
Normal file
263
dDashboardServer/blueprints/CharactersBlueprint.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
#include "CharactersBlueprint.h"
|
||||
#include "Database.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "ePermissionMap.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace CharactersBlueprint {
|
||||
|
||||
// Helper function to get current user's account info from session
|
||||
std::optional<IAccounts::Info> GetCurrentUser(const crow::request& req, DashboardApp& app) {
|
||||
auto& session = app.get_context<Session>(const_cast<crow::request&>(req));
|
||||
std::string username = session.template get<std::string>("username");
|
||||
|
||||
if (username.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return Database::Get()->GetAccountInfo(username);
|
||||
}
|
||||
|
||||
// Helper function to get user's GM level
|
||||
eGameMasterLevel GetUserGMLevel(const crow::request& req, DashboardApp& app) {
|
||||
auto user = GetCurrentUser(req, app);
|
||||
if (!user) {
|
||||
return eGameMasterLevel::CIVILIAN;
|
||||
}
|
||||
return user->maxGmLevel;
|
||||
}
|
||||
|
||||
// Helper function to check if user has minimum GM level
|
||||
bool HasMinimumGMLevel(const crow::request& req, DashboardApp& app, eGameMasterLevel required) {
|
||||
auto level = GetUserGMLevel(req, app);
|
||||
return static_cast<uint8_t>(level) >= static_cast<uint8_t>(required);
|
||||
}
|
||||
|
||||
// Helper to check if user can access a character (owns it or is GM 3+)
|
||||
bool CanAccessCharacter(const crow::request& req, DashboardApp& app, LWOOBJID characterId) {
|
||||
auto user = GetCurrentUser(req, app);
|
||||
if (!user) return false;
|
||||
|
||||
// GMs can access any character
|
||||
if (static_cast<uint8_t>(user->maxGmLevel) >= static_cast<uint8_t>(eGameMasterLevel::MODERATOR)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if user owns this character
|
||||
auto charInfo = Database::Get()->GetCharacterInfo(characterId);
|
||||
if (charInfo && charInfo->accountId == user->id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Setup(DashboardApp& app) {
|
||||
// Get character by ID
|
||||
CROW_ROUTE(app, "/api/characters/<uint>")
|
||||
.methods("GET"_method)
|
||||
([&](const crow::request& req, uint64_t character_id) {
|
||||
if (!CanAccessCharacter(req, app, character_id)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
auto charInfo = Database::Get()->GetCharacterInfo(character_id);
|
||||
if (!charInfo) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Character not found";
|
||||
return crow::response(404, response);
|
||||
}
|
||||
|
||||
response["success"] = true;
|
||||
response["id"] = static_cast<uint64_t>(charInfo->id);
|
||||
response["name"] = charInfo->name;
|
||||
response["pending_name"] = charInfo->pendingName;
|
||||
response["account_id"] = charInfo->accountId;
|
||||
response["needs_rename"] = charInfo->needsRename;
|
||||
response["clone_id"] = static_cast<uint64_t>(charInfo->cloneId);
|
||||
response["permission_map"] = static_cast<uint64_t>(charInfo->permissionMap);
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Get character XML
|
||||
CROW_ROUTE(app, "/api/characters/<uint>/xml")
|
||||
.methods("GET"_method)
|
||||
([&](const crow::request& req, uint64_t character_id) {
|
||||
if (!CanAccessCharacter(req, app, character_id)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
try {
|
||||
auto xml = Database::Get()->GetCharacterXml(character_id);
|
||||
|
||||
auto res = crow::response(xml);
|
||||
res.set_header("Content-Type", "application/xml");
|
||||
res.set_header("Content-Disposition", "attachment; filename=\"character_" + std::to_string(character_id) + ".xml\"");
|
||||
return res;
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
crow::json::wvalue response;
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
return crow::response(500, response);
|
||||
}
|
||||
});
|
||||
|
||||
// Rescue character (teleport to safe zone)
|
||||
CROW_ROUTE(app, "/api/characters/<uint>/rescue")
|
||||
.methods("POST"_method)
|
||||
([&](const crow::request& req, uint64_t character_id) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
auto body = crow::json::load(req.body);
|
||||
if (!body) {
|
||||
return crow::response(400, "{\"error\": \"Invalid JSON\"}");
|
||||
}
|
||||
|
||||
uint32_t zoneId = 1200; // Default to Avant Gardens
|
||||
if (body.has("zone_id")) {
|
||||
zoneId = body["zone_id"].i();
|
||||
}
|
||||
|
||||
// RescueCharacter logic removed; this server does not perform live rescues.
|
||||
// Return not-implemented to indicate the operation must be performed via the chat server.
|
||||
response["success"] = false;
|
||||
response["error"] = "Rescue character not implemented on this server. Use chat server tools.";
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Toggle character restrictions (trade, mail, chat)
|
||||
CROW_ROUTE(app, "/api/characters/<uint>/restrict/<int>")
|
||||
.methods("POST"_method)
|
||||
([&](const crow::request& req, uint64_t character_id, int restriction_bit) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
auto charInfo = Database::Get()->GetCharacterInfo(character_id);
|
||||
if (!charInfo) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Character not found";
|
||||
return crow::response(404, response);
|
||||
}
|
||||
|
||||
// Toggle the restriction bit
|
||||
uint64_t currentPerms = static_cast<uint64_t>(charInfo->permissionMap);
|
||||
uint64_t newPerms = currentPerms ^ (1ULL << restriction_bit);
|
||||
|
||||
Database::Get()->UpdateCharacterPermissions(character_id, static_cast<ePermissionMap>(newPerms));
|
||||
|
||||
response["success"] = true;
|
||||
response["permission_map"] = newPerms;
|
||||
response["message"] = "Character restrictions updated";
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Force character rename
|
||||
CROW_ROUTE(app, "/api/characters/<uint>/force-rename")
|
||||
.methods("POST"_method)
|
||||
([&](const crow::request& req, uint64_t character_id) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
auto charInfo = Database::Get()->GetCharacterInfo(character_id);
|
||||
if (!charInfo) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Character not found";
|
||||
return crow::response(404, response);
|
||||
}
|
||||
|
||||
Database::Get()->SetCharacterNeedsRename(character_id, true);
|
||||
|
||||
response["success"] = true;
|
||||
response["message"] = "Character will be forced to rename on next login";
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Set character name (admin override)
|
||||
CROW_ROUTE(app, "/api/characters/<uint>/set-name")
|
||||
.methods("POST"_method)
|
||||
([&](const crow::request& req, uint64_t character_id) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::DEVELOPER)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
auto body = crow::json::load(req.body);
|
||||
if (!body) {
|
||||
return crow::response(400, "{\"error\": \"Invalid JSON\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
std::string newName = body["name"].s();
|
||||
|
||||
if (newName.empty() || newName.length() > 33) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Invalid name length (must be 1-33 characters)";
|
||||
return crow::response(response);
|
||||
}
|
||||
|
||||
// Check if name is already in use
|
||||
if (Database::Get()->IsNameInUse(newName)) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Name is already in use";
|
||||
return crow::response(response);
|
||||
}
|
||||
|
||||
Database::Get()->SetCharacterName(character_id, newName);
|
||||
Database::Get()->SetPendingCharacterName(character_id, "");
|
||||
Database::Get()->SetCharacterNeedsRename(character_id, false);
|
||||
|
||||
response["success"] = true;
|
||||
response["message"] = "Character name updated successfully";
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace CharactersBlueprint
|
||||
20
dDashboardServer/blueprints/CharactersBlueprint.h
Normal file
20
dDashboardServer/blueprints/CharactersBlueprint.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef __CHARACTERSBLUEPRINT_H__
|
||||
#define __CHARACTERSBLUEPRINT_H__
|
||||
|
||||
#include "crow.h"
|
||||
#include "crow/middlewares/session.h"
|
||||
|
||||
namespace CharactersBlueprint {
|
||||
|
||||
using Session = crow::SessionMiddleware<crow::InMemoryStore>;
|
||||
using DashboardApp = crow::App<crow::CookieParser, Session>;
|
||||
|
||||
/**
|
||||
* Setup character management routes
|
||||
* Registers routes for viewing, editing, and managing characters
|
||||
*/
|
||||
void Setup(DashboardApp& app);
|
||||
|
||||
} // namespace CharactersBlueprint
|
||||
|
||||
#endif // __CHARACTERSBLUEPRINT_H__
|
||||
207
dDashboardServer/blueprints/MailBlueprint.cpp
Normal file
207
dDashboardServer/blueprints/MailBlueprint.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
#include "MailBlueprint.h"
|
||||
#include "Database.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "MailInfo.h"
|
||||
#include "Logger.h"
|
||||
#include <ctime>
|
||||
|
||||
namespace MailBlueprint {
|
||||
|
||||
// Helper function to get current user's account info from session
|
||||
std::optional<IAccounts::Info> GetCurrentUser(const crow::request& req, DashboardApp& app) {
|
||||
auto& session = app.get_context<Session>(const_cast<crow::request&>(req));
|
||||
std::string username = session.template get<std::string>("username");
|
||||
|
||||
if (username.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return Database::Get()->GetAccountInfo(username);
|
||||
}
|
||||
|
||||
// Helper function to get user's GM level
|
||||
eGameMasterLevel GetUserGMLevel(const crow::request& req, DashboardApp& app) {
|
||||
auto user = GetCurrentUser(req, app);
|
||||
if (!user) {
|
||||
return eGameMasterLevel::CIVILIAN;
|
||||
}
|
||||
return user->maxGmLevel;
|
||||
}
|
||||
|
||||
// Helper function to check if user has minimum GM level
|
||||
bool HasMinimumGMLevel(const crow::request& req, DashboardApp& app, eGameMasterLevel required) {
|
||||
auto level = GetUserGMLevel(req, app);
|
||||
return static_cast<uint8_t>(level) >= static_cast<uint8_t>(required);
|
||||
}
|
||||
|
||||
void Setup(DashboardApp& app) {
|
||||
// Send mail to a character or all characters
|
||||
CROW_ROUTE(app, "/api/mail/send")
|
||||
.methods("POST"_method)
|
||||
([&](const crow::request& req) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
auto body = crow::json::load(req.body);
|
||||
if (!body) {
|
||||
return crow::response(400, "{\"error\": \"Invalid JSON\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
auto user = GetCurrentUser(req, app);
|
||||
if (!user) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Not authenticated";
|
||||
return crow::response(401, response);
|
||||
}
|
||||
|
||||
auto& session = app.get_context<Session>(const_cast<crow::request&>(req));
|
||||
std::string username = session.template get<std::string>("username");
|
||||
|
||||
// Get mail parameters
|
||||
std::string subject;
|
||||
if (body.has("subject"))
|
||||
subject = std::string(body["subject"].s());
|
||||
else
|
||||
subject = "";
|
||||
|
||||
std::string message;
|
||||
if (body.has("body"))
|
||||
message = std::string(body["body"].s());
|
||||
else
|
||||
message = "";
|
||||
int64_t recipientId = body.has("recipient_id") ? body["recipient_id"].i() : 0;
|
||||
bool sendToAll = body.has("send_to_all") ? body["send_to_all"].b() : false;
|
||||
|
||||
// Item attachment (optional)
|
||||
int32_t itemLot = body.has("attachment_lot") ? body["attachment_lot"].i() : 0;
|
||||
int32_t itemCount = body.has("attachment_count") ? body["attachment_count"].i() : 0;
|
||||
|
||||
if (subject.empty() || message.empty()) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Subject and body are required";
|
||||
return crow::response(response);
|
||||
}
|
||||
|
||||
// Prefix sender name with [GM]
|
||||
std::string senderName = "[GM] " + username;
|
||||
|
||||
std::vector<LWOOBJID> recipients;
|
||||
|
||||
if (sendToAll) {
|
||||
// Get all accounts and their characters
|
||||
auto allAccounts = Database::Get()->GetAllAccounts();
|
||||
for (const auto& acct : allAccounts) {
|
||||
auto chars = Database::Get()->GetAccountCharacterIds(acct.id);
|
||||
for (const auto& charId : chars) {
|
||||
recipients.push_back(charId);
|
||||
}
|
||||
}
|
||||
} else if (recipientId > 0) {
|
||||
recipients.push_back(recipientId);
|
||||
} else {
|
||||
response["success"] = false;
|
||||
response["error"] = "No recipients specified";
|
||||
return crow::response(response);
|
||||
}
|
||||
|
||||
// Send mail to all recipients
|
||||
uint64_t currentTime = static_cast<uint64_t>(std::time(nullptr));
|
||||
int mailSent = 0;
|
||||
|
||||
for (const auto& recipId : recipients) {
|
||||
// Get recipient character name
|
||||
auto charInfo = Database::Get()->GetCharacterInfo(recipId);
|
||||
if (!charInfo) continue;
|
||||
|
||||
MailInfo mail;
|
||||
mail.senderUsername = senderName;
|
||||
mail.recipient = charInfo->name;
|
||||
mail.receiverId = recipId;
|
||||
mail.subject = subject;
|
||||
mail.body = message;
|
||||
mail.itemID = itemLot > 0 ? 1 : 0; // If there's an item, set ID to 1
|
||||
mail.itemLOT = itemLot;
|
||||
mail.itemCount = itemCount > 0 ? itemCount : 1;
|
||||
mail.timeSent = currentTime;
|
||||
mail.wasRead = false;
|
||||
|
||||
Database::Get()->InsertNewMail(mail);
|
||||
mailSent++;
|
||||
}
|
||||
|
||||
response["success"] = true;
|
||||
response["message"] = "Mail sent successfully";
|
||||
response["recipients"] = mailSent;
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Get mail by ID (for viewing)
|
||||
CROW_ROUTE(app, "/api/mail/<uint>")
|
||||
.methods("GET"_method)
|
||||
([&](const crow::request& req, uint64_t mail_id) {
|
||||
// Any authenticated user can view mail
|
||||
auto user = GetCurrentUser(req, app);
|
||||
if (!user) {
|
||||
return crow::response(401, "{\"error\": \"Not authenticated\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
auto mail = Database::Get()->GetMail(mail_id);
|
||||
if (!mail) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Mail not found";
|
||||
return crow::response(404, response);
|
||||
}
|
||||
|
||||
// Check if user can access this mail (owns the character or is GM)
|
||||
auto charInfo = Database::Get()->GetCharacterInfo(mail->receiverId);
|
||||
bool canAccess = false;
|
||||
|
||||
if (charInfo && charInfo->accountId == user->id) {
|
||||
canAccess = true;
|
||||
}
|
||||
|
||||
if (static_cast<uint8_t>(user->maxGmLevel) >= static_cast<uint8_t>(eGameMasterLevel::MODERATOR)) {
|
||||
canAccess = true;
|
||||
}
|
||||
|
||||
if (!canAccess) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Access denied";
|
||||
return crow::response(403, response);
|
||||
}
|
||||
|
||||
response["success"] = true;
|
||||
response["id"] = mail->id;
|
||||
response["sender_name"] = mail->senderUsername;
|
||||
response["receiver_name"] = mail->recipient;
|
||||
response["receiver_id"] = static_cast<uint64_t>(mail->receiverId);
|
||||
response["subject"] = mail->subject;
|
||||
response["body"] = mail->body;
|
||||
response["attachment_lot"] = mail->itemLOT;
|
||||
response["attachment_count"] = mail->itemCount;
|
||||
response["time_sent"] = mail->timeSent;
|
||||
response["was_read"] = mail->wasRead;
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace MailBlueprint
|
||||
20
dDashboardServer/blueprints/MailBlueprint.h
Normal file
20
dDashboardServer/blueprints/MailBlueprint.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef __MAILBLUEPRINT_H__
|
||||
#define __MAILBLUEPRINT_H__
|
||||
|
||||
#include "crow.h"
|
||||
#include "crow/middlewares/session.h"
|
||||
|
||||
namespace MailBlueprint {
|
||||
|
||||
using Session = crow::SessionMiddleware<crow::InMemoryStore>;
|
||||
using DashboardApp = crow::App<crow::CookieParser, Session>;
|
||||
|
||||
/**
|
||||
* Setup mail management routes
|
||||
* Registers routes for sending and viewing mail
|
||||
*/
|
||||
void Setup(DashboardApp& app);
|
||||
|
||||
} // namespace MailBlueprint
|
||||
|
||||
#endif // __MAILBLUEPRINT_H__
|
||||
279
dDashboardServer/blueprints/ModerationBlueprint.cpp
Normal file
279
dDashboardServer/blueprints/ModerationBlueprint.cpp
Normal file
@@ -0,0 +1,279 @@
|
||||
#include "ModerationBlueprint.h"
|
||||
#include "Database.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "Logger.h"
|
||||
|
||||
namespace ModerationBlueprint {
|
||||
|
||||
// Helper function to get current user's account info from session
|
||||
std::optional<IAccounts::Info> GetCurrentUser(const crow::request& req, DashboardApp& app) {
|
||||
auto& session = app.get_context<Session>(const_cast<crow::request&>(req));
|
||||
std::string username = session.template get<std::string>("username");
|
||||
|
||||
if (username.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return Database::Get()->GetAccountInfo(username);
|
||||
}
|
||||
|
||||
// Helper function to check if user has minimum GM level
|
||||
bool HasMinimumGMLevel(const crow::request& req, DashboardApp& app, eGameMasterLevel required) {
|
||||
auto user = GetCurrentUser(req, app);
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
return static_cast<uint8_t>(user->maxGmLevel) >= static_cast<uint8_t>(required);
|
||||
}
|
||||
|
||||
void Setup(DashboardApp& app) {
|
||||
// Get pet names by status
|
||||
CROW_ROUTE(app, "/api/moderation/pets")
|
||||
.methods("GET"_method)
|
||||
([&](const crow::request& req) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
crow::json::wvalue::list data;
|
||||
|
||||
try {
|
||||
auto statusParam = req.url_params.get("status");
|
||||
std::string status = statusParam ? statusParam : "all";
|
||||
|
||||
std::vector<IPetNames::DetailedInfo> pets;
|
||||
|
||||
if (status == "approved") {
|
||||
pets = Database::Get()->GetPetNamesByStatus(2);
|
||||
} else if (status == "unapproved") {
|
||||
pets = Database::Get()->GetPetNamesByStatus(1);
|
||||
} else {
|
||||
pets = Database::Get()->GetAllPetNames();
|
||||
}
|
||||
|
||||
for (const auto& pet : pets) {
|
||||
crow::json::wvalue item;
|
||||
item["id"] = static_cast<uint64_t>(pet.id);
|
||||
item["pet_name"] = pet.petName;
|
||||
item["approval_status"] = pet.approvalStatus;
|
||||
item["owner_id"] = static_cast<uint64_t>(pet.ownerId);
|
||||
|
||||
// Get owner character name
|
||||
if (pet.ownerId > 0) {
|
||||
auto charInfo = Database::Get()->GetCharacterInfo(pet.ownerId);
|
||||
if (charInfo) {
|
||||
item["owner_name"] = charInfo->name;
|
||||
} else {
|
||||
item["owner_name"] = "Unknown";
|
||||
}
|
||||
} else {
|
||||
item["owner_name"] = "None";
|
||||
}
|
||||
|
||||
data.push_back(std::move(item));
|
||||
}
|
||||
|
||||
response["data"] = std::move(data);
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["error"] = ex.what();
|
||||
return crow::response(500, response);
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Approve a pet name
|
||||
CROW_ROUTE(app, "/api/moderation/pets/<uint>/approve")
|
||||
.methods("POST"_method)
|
||||
([&](const crow::request& req, uint64_t pet_id) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
Database::Get()->SetPetApprovalStatus(pet_id, 2); // 2 = approved
|
||||
|
||||
response["success"] = true;
|
||||
response["message"] = "Pet name approved";
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Reject a pet name
|
||||
CROW_ROUTE(app, "/api/moderation/pets/<uint>/reject")
|
||||
.methods("POST"_method)
|
||||
([&](const crow::request& req, uint64_t pet_id) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
Database::Get()->SetPetApprovalStatus(pet_id, 0); // 0 = rejected
|
||||
|
||||
response["success"] = true;
|
||||
response["message"] = "Pet name rejected";
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Get properties by approval status
|
||||
CROW_ROUTE(app, "/api/moderation/properties")
|
||||
.methods("GET"_method)
|
||||
([&](const crow::request& req) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
crow::json::wvalue::list data;
|
||||
|
||||
try {
|
||||
auto statusParam = req.url_params.get("status");
|
||||
std::string status = statusParam ? statusParam : "all";
|
||||
|
||||
std::vector<IProperty::Info> properties;
|
||||
|
||||
if (status == "approved") {
|
||||
properties = Database::Get()->GetPropertiesByApprovalStatus(1);
|
||||
} else if (status == "unapproved") {
|
||||
properties = Database::Get()->GetPropertiesByApprovalStatus(0);
|
||||
} else {
|
||||
properties = Database::Get()->GetAllProperties();
|
||||
}
|
||||
|
||||
for (const auto& prop : properties) {
|
||||
crow::json::wvalue item;
|
||||
item["id"] = static_cast<uint64_t>(prop.id);
|
||||
item["name"] = prop.name;
|
||||
item["description"] = prop.description;
|
||||
item["owner_id"] = static_cast<uint64_t>(prop.ownerId);
|
||||
item["clone_id"] = static_cast<uint64_t>(prop.cloneId);
|
||||
item["privacy_option"] = prop.privacyOption;
|
||||
item["mod_approved"] = prop.modApproved;
|
||||
item["last_updated"] = prop.lastUpdatedTime;
|
||||
item["claimed_time"] = prop.claimedTime;
|
||||
item["reputation"] = prop.reputation;
|
||||
item["performance_cost"] = prop.performanceCost;
|
||||
item["rejection_reason"] = prop.rejectionReason;
|
||||
|
||||
// Get owner character name
|
||||
auto charInfo = Database::Get()->GetCharacterInfo(prop.ownerId);
|
||||
if (charInfo) {
|
||||
item["owner_name"] = charInfo->name;
|
||||
} else {
|
||||
item["owner_name"] = "Unknown";
|
||||
}
|
||||
|
||||
data.push_back(std::move(item));
|
||||
}
|
||||
|
||||
response["data"] = std::move(data);
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["error"] = ex.what();
|
||||
return crow::response(500, response);
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Approve/unapprove a property
|
||||
CROW_ROUTE(app, "/api/moderation/properties/<uint>/approve")
|
||||
.methods("POST"_method)
|
||||
([&](const crow::request& req, uint64_t property_id) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
auto prop = Database::Get()->GetPropertyInfo(property_id);
|
||||
if (!prop) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Property not found";
|
||||
return crow::response(404, response);
|
||||
}
|
||||
|
||||
// Toggle approval
|
||||
IProperty::Info updatedInfo = *prop;
|
||||
updatedInfo.modApproved = prop->modApproved ? 0 : 1;
|
||||
updatedInfo.rejectionReason = "";
|
||||
|
||||
Database::Get()->UpdatePropertyModerationInfo(updatedInfo);
|
||||
|
||||
response["success"] = true;
|
||||
response["approved"] = updatedInfo.modApproved;
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Reject a property with reason
|
||||
CROW_ROUTE(app, "/api/moderation/properties/<uint>/reject")
|
||||
.methods("POST"_method)
|
||||
([&](const crow::request& req, uint64_t property_id) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
auto body = crow::json::load(req.body);
|
||||
if (!body) {
|
||||
return crow::response(400, "{\"error\": \"Invalid JSON\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
auto prop = Database::Get()->GetPropertyInfo(property_id);
|
||||
if (!prop) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Property not found";
|
||||
return crow::response(404, response);
|
||||
}
|
||||
|
||||
std::string reason;
|
||||
if (body.has("reason"))
|
||||
reason = std::string(body["reason"].s());
|
||||
else
|
||||
reason = "No reason provided";
|
||||
|
||||
IProperty::Info updatedInfo = *prop;
|
||||
updatedInfo.modApproved = 0;
|
||||
updatedInfo.rejectionReason = reason;
|
||||
|
||||
Database::Get()->UpdatePropertyModerationInfo(updatedInfo);
|
||||
|
||||
response["success"] = true;
|
||||
response["message"] = "Property rejected";
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace ModerationBlueprint
|
||||
20
dDashboardServer/blueprints/ModerationBlueprint.h
Normal file
20
dDashboardServer/blueprints/ModerationBlueprint.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef __MODERATIONBLUEPRINT_H__
|
||||
#define __MODERATIONBLUEPRINT_H__
|
||||
|
||||
#include "crow.h"
|
||||
#include "crow/middlewares/session.h"
|
||||
|
||||
namespace ModerationBlueprint {
|
||||
|
||||
using Session = crow::SessionMiddleware<crow::InMemoryStore>;
|
||||
using DashboardApp = crow::App<crow::CookieParser, Session>;
|
||||
|
||||
/**
|
||||
* Setup moderation routes
|
||||
* Registers routes for pet name moderation and property approval
|
||||
*/
|
||||
void Setup(DashboardApp& app);
|
||||
|
||||
} // namespace ModerationBlueprint
|
||||
|
||||
#endif // __MODERATIONBLUEPRINT_H__
|
||||
380
dDashboardServer/blueprints/PageBlueprint.cpp
Normal file
380
dDashboardServer/blueprints/PageBlueprint.cpp
Normal file
@@ -0,0 +1,380 @@
|
||||
#include "PageBlueprint.h"
|
||||
#include "Logger.h"
|
||||
#include "Database.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
|
||||
namespace PageBlueprint {
|
||||
|
||||
// Helper to get GM level name
|
||||
std::string GetGMLevelName(eGameMasterLevel level) {
|
||||
switch (level) {
|
||||
case eGameMasterLevel::CIVILIAN: return "Civilian";
|
||||
case eGameMasterLevel::FORUM_MODERATOR: return "Forum Moderator";
|
||||
case eGameMasterLevel::JUNIOR_MODERATOR: return "Junior Moderator";
|
||||
case eGameMasterLevel::MODERATOR: return "Moderator";
|
||||
case eGameMasterLevel::SENIOR_MODERATOR: return "Senior Moderator";
|
||||
case eGameMasterLevel::LEAD_MODERATOR: return "Lead Moderator";
|
||||
case eGameMasterLevel::JUNIOR_DEVELOPER: return "Junior Developer";
|
||||
case eGameMasterLevel::INACTIVE_DEVELOPER: return "Inactive Developer";
|
||||
case eGameMasterLevel::DEVELOPER: return "Developer";
|
||||
case eGameMasterLevel::OPERATOR: return "Operator";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to get current user's account info from session
|
||||
std::optional<IAccounts::Info> GetCurrentUser(const crow::request& req, DashboardApp& app) {
|
||||
auto& session = app.get_context<Session>(const_cast<crow::request&>(req));
|
||||
std::string username = session.template get<std::string>("username");
|
||||
|
||||
if (username.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return Database::Get()->GetAccountInfo(username);
|
||||
}
|
||||
|
||||
// Helper to get user's GM level
|
||||
eGameMasterLevel GetUserGMLevel(const crow::request& req, DashboardApp& app) {
|
||||
auto user = GetCurrentUser(req, app);
|
||||
if (!user) {
|
||||
return eGameMasterLevel::CIVILIAN;
|
||||
}
|
||||
return user->maxGmLevel;
|
||||
}
|
||||
|
||||
// Helper to check if user has minimum GM level
|
||||
bool HasMinimumGMLevel(const crow::request& req, DashboardApp& app, eGameMasterLevel required) {
|
||||
auto level = GetUserGMLevel(req, app);
|
||||
return static_cast<uint8_t>(level) >= static_cast<uint8_t>(required);
|
||||
}
|
||||
|
||||
// Helper to create base context for all templates
|
||||
crow::mustache::context GetBaseContext(const crow::request& req, DashboardApp& app) {
|
||||
crow::mustache::context ctx;
|
||||
|
||||
auto& session = app.get_context<Session>(const_cast<crow::request&>(req));
|
||||
std::string username = session.template get<std::string>("username");
|
||||
int account_id = session.template get<int>("account_id", -1);
|
||||
int gm_level = session.template get<int>("gm_level", -1);
|
||||
|
||||
if (!username.empty() && account_id != -1) {
|
||||
LOG("User '%s' (Account ID: %d) is authenticated with GM level %d", username.c_str(), account_id, gm_level);
|
||||
ctx["is_authenticated"] = true;
|
||||
ctx["show_navbar"] = true;
|
||||
ctx["username"] = username;
|
||||
ctx["account_id"] = account_id;
|
||||
ctx["gm_level"] = gm_level;
|
||||
ctx["gm_level_name"] = GetGMLevelName(static_cast<eGameMasterLevel>(gm_level));
|
||||
|
||||
// Set permission flags
|
||||
ctx["is_gm_3_plus"] = (gm_level >= 3);
|
||||
ctx["is_gm_5_plus"] = (gm_level >= 5);
|
||||
ctx["is_gm_8_plus"] = (gm_level >= 8);
|
||||
ctx["is_gm_9_plus"] = (gm_level >= 9);
|
||||
} else {
|
||||
LOG("User is not authenticated");
|
||||
ctx["is_authenticated"] = false;
|
||||
ctx["show_navbar"] = false;
|
||||
}
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// Helper to render a page with layout
|
||||
std::string RenderPage(const crow::request& req, DashboardApp& app, const std::string& template_name, const std::string& page_title, crow::mustache::context& page_ctx) {
|
||||
auto base_ctx = GetBaseContext(req, app);
|
||||
|
||||
// Merge base context with page-specific context
|
||||
for (const auto& key : page_ctx.keys()) {
|
||||
base_ctx[key] = crow::json::wvalue(page_ctx[key]);
|
||||
}
|
||||
|
||||
// Load the content template and render to string
|
||||
auto content_page = crow::mustache::load(template_name);
|
||||
std::string content_html = content_page.render_string(base_ctx);
|
||||
|
||||
// Set content and page title in base context
|
||||
base_ctx["content"] = crow::json::wvalue(content_html);
|
||||
base_ctx["page_title"] = crow::json::wvalue(page_title);
|
||||
|
||||
// Render with layout
|
||||
auto layout = crow::mustache::load("layouts/base.html");
|
||||
return layout.render_string(base_ctx);
|
||||
}
|
||||
|
||||
void Setup(DashboardApp& app) {
|
||||
// Home/Dashboard page
|
||||
CROW_ROUTE(app, "/")
|
||||
([&](const crow::request& req) {
|
||||
crow::mustache::context ctx;
|
||||
ctx["nav_home"] = true;
|
||||
|
||||
std::string html = RenderPage(req, app, "index.html", "Dashboard", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Login page
|
||||
CROW_ROUTE(app, "/login")
|
||||
([&](const crow::request& req) {
|
||||
crow::mustache::context ctx;
|
||||
|
||||
std::string html = RenderPage(req, app, "login.html", "Login", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Accounts page
|
||||
CROW_ROUTE(app, "/accounts")
|
||||
([&](const crow::request& req) {
|
||||
// Check GM level
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "Forbidden - Insufficient GM level");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
ctx["nav_accounts"] = true;
|
||||
|
||||
std::string html = RenderPage(req, app, "accounts/index.html", "Accounts", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Activity Logs page
|
||||
CROW_ROUTE(app, "/logs/activities")
|
||||
([&](const crow::request& req) {
|
||||
// Check GM level - Developers and above
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::DEVELOPER)) {
|
||||
return crow::response(403, "Forbidden - Insufficient GM level");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
// Set nav active state if needed
|
||||
|
||||
std::string html = RenderPage(req, app, "logs/activities.html", "Activity Logs", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Characters page
|
||||
CROW_ROUTE(app, "/characters")
|
||||
([&](const crow::request& req) {
|
||||
// Check GM level - Moderators and above
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "Forbidden - Insufficient GM level");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
ctx["nav_characters"] = true;
|
||||
|
||||
std::string html = RenderPage(req, app, "characters/index.html", "Characters", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Play Keys page
|
||||
CROW_ROUTE(app, "/playkeys")
|
||||
([&](const crow::request& req) {
|
||||
// Check GM level - Lead Moderators and above
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::LEAD_MODERATOR)) {
|
||||
return crow::response(403, "Forbidden - Insufficient GM level");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
ctx["nav_playkeys"] = true;
|
||||
|
||||
std::string html = RenderPage(req, app, "playkeys/index.html", "Play Keys", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Registration page - public
|
||||
CROW_ROUTE(app, "/register")
|
||||
([&](const crow::request& req) {
|
||||
crow::mustache::context ctx;
|
||||
std::string html = RenderPage(req, app, "register.html", "Register", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Mail page
|
||||
CROW_ROUTE(app, "/mail/send")
|
||||
([&](const crow::request& req) {
|
||||
// Check GM level - Moderators and above
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "Forbidden - Insufficient GM level");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
ctx["nav_mail"] = true;
|
||||
|
||||
std::string html = RenderPage(req, app, "mail/send.html", "Send Mail", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Bug Reports page
|
||||
CROW_ROUTE(app, "/bugreports")
|
||||
([&](const crow::request& req) {
|
||||
// Anyone authenticated can view their own bug reports
|
||||
// GMs can view all
|
||||
auto user = GetCurrentUser(req, app);
|
||||
if (!user) {
|
||||
return crow::response(403, "Forbidden - Login required");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
ctx["nav_bugreports"] = true;
|
||||
|
||||
std::string html = RenderPage(req, app, "bugreports/index.html", "Bug Reports", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Moderation page - Pet Names
|
||||
CROW_ROUTE(app, "/moderation/pets")
|
||||
([&](const crow::request& req) {
|
||||
// Check GM level - Moderators and above
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "Forbidden - Insufficient GM level");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
ctx["nav_moderation"] = true;
|
||||
|
||||
std::string html = RenderPage(req, app, "moderation/pets.html", "Pet Name Moderation", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Moderation page - Properties
|
||||
CROW_ROUTE(app, "/moderation/properties")
|
||||
([&](const crow::request& req) {
|
||||
// Check GM level - Moderators and above
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "Forbidden - Insufficient GM level");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
ctx["nav_moderation"] = true;
|
||||
|
||||
std::string html = RenderPage(req, app, "moderation/properties.html", "Property Moderation", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Account view page
|
||||
CROW_ROUTE(app, "/accounts/view/<int>")
|
||||
([&](const crow::request& req, int account_id) {
|
||||
// Check GM level - Moderators and above
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "Forbidden - Insufficient GM level");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
ctx["nav_accounts"] = true;
|
||||
ctx["account_id"] = account_id;
|
||||
|
||||
std::string html = RenderPage(req, app, "accounts/view.html", "View Account", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Character view page
|
||||
CROW_ROUTE(app, "/characters/view/<int>")
|
||||
([&](const crow::request& req, int character_id) {
|
||||
// Check GM level - Moderators and above
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "Forbidden - Insufficient GM level");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
ctx["nav_characters"] = true;
|
||||
ctx["character_id"] = character_id;
|
||||
|
||||
std::string html = RenderPage(req, app, "characters/view.html", "View Character", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Logs - Command Logs page
|
||||
CROW_ROUTE(app, "/logs/commands")
|
||||
([&](const crow::request& req) {
|
||||
// Check GM level - Developers and above
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::DEVELOPER)) {
|
||||
return crow::response(403, "Forbidden - Insufficient GM level");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
// Set nav active state if needed
|
||||
|
||||
std::string html = RenderPage(req, app, "logs/commands.html", "Command Logs", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Logs - Audit Logs page
|
||||
CROW_ROUTE(app, "/logs/audits")
|
||||
([&](const crow::request& req) {
|
||||
// Check GM level - Developers and above
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::DEVELOPER)) {
|
||||
return crow::response(403, "Forbidden - Insufficient GM level");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
// Set nav active state if needed
|
||||
|
||||
std::string html = RenderPage(req, app, "logs/audits.html", "Audit Logs", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// About page
|
||||
CROW_ROUTE(app, "/about")
|
||||
([&](const crow::request& req) {
|
||||
auto user = GetCurrentUser(req, app);
|
||||
if (!user) {
|
||||
return crow::response(403, "Forbidden - Login required");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
|
||||
std::string html = RenderPage(req, app, "about.html", "About", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Bug Reports page (fix routing)
|
||||
CROW_ROUTE(app, "/bugs")
|
||||
([&](const crow::request& req) {
|
||||
// Anyone authenticated can view their own bug reports
|
||||
// GMs can view all
|
||||
auto user = GetCurrentUser(req, app);
|
||||
if (!user) {
|
||||
return crow::response(403, "Forbidden - Login required");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
ctx["nav_bugs"] = true;
|
||||
|
||||
std::string html = RenderPage(req, app, "bugreports/index.html", "Bug Reports", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Moderation page - Pending Pets
|
||||
CROW_ROUTE(app, "/moderation/pending")
|
||||
([&](const crow::request& req) {
|
||||
// Check GM level - Moderators and above
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "Forbidden - Insufficient GM level");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
ctx["nav_moderation"] = true;
|
||||
|
||||
std::string html = RenderPage(req, app, "moderation/pets.html", "Pending Pet Names", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
|
||||
// Properties page
|
||||
CROW_ROUTE(app, "/properties")
|
||||
([&](const crow::request& req) {
|
||||
// Check GM level - Moderators and above
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::MODERATOR)) {
|
||||
return crow::response(403, "Forbidden - Insufficient GM level");
|
||||
}
|
||||
|
||||
crow::mustache::context ctx;
|
||||
ctx["nav_moderation"] = true;
|
||||
|
||||
std::string html = RenderPage(req, app, "moderation/properties.html", "Property Moderation", ctx);
|
||||
return crow::response(html);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace PageBlueprint
|
||||
17
dDashboardServer/blueprints/PageBlueprint.h
Normal file
17
dDashboardServer/blueprints/PageBlueprint.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "crow.h"
|
||||
#include "crow/middlewares/session.h"
|
||||
|
||||
namespace PageBlueprint {
|
||||
|
||||
using Session = crow::SessionMiddleware<crow::InMemoryStore>;
|
||||
using DashboardApp = crow::App<crow::CookieParser, Session>;
|
||||
|
||||
/**
|
||||
* Setup page rendering routes
|
||||
* Registers routes that render HTML pages (dashboard, login, accounts, etc.)
|
||||
*/
|
||||
void Setup(DashboardApp& app);
|
||||
|
||||
} // namespace PageBlueprint
|
||||
288
dDashboardServer/blueprints/PlayKeysBlueprint.cpp
Normal file
288
dDashboardServer/blueprints/PlayKeysBlueprint.cpp
Normal file
@@ -0,0 +1,288 @@
|
||||
#include "PlayKeysBlueprint.h"
|
||||
#include "Database.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "Logger.h"
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
namespace PlayKeysBlueprint {
|
||||
|
||||
// Helper to generate a random play key string (format: XXXX-XXXX-XXXX-XXXX)
|
||||
std::string GeneratePlayKeyString() {
|
||||
static const char charset[] = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // Excluding ambiguous chars
|
||||
static std::random_device rd;
|
||||
static std::mt19937 gen(rd());
|
||||
static std::uniform_int_distribution<> dis(0, sizeof(charset) - 2);
|
||||
|
||||
std::stringstream ss;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (i > 0 && i % 4 == 0) ss << '-';
|
||||
ss << charset[dis(gen)];
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// Helper function to get current user's account info from session
|
||||
std::optional<IAccounts::Info> GetCurrentUser(const crow::request& req, DashboardApp& app) {
|
||||
auto& session = app.get_context<Session>(const_cast<crow::request&>(req));
|
||||
std::string username = session.template get<std::string>("username");
|
||||
|
||||
if (username.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return Database::Get()->GetAccountInfo(username);
|
||||
}
|
||||
|
||||
// Helper function to get user's GM level
|
||||
eGameMasterLevel GetUserGMLevel(const crow::request& req, DashboardApp& app) {
|
||||
auto user = GetCurrentUser(req, app);
|
||||
if (!user) {
|
||||
return eGameMasterLevel::CIVILIAN;
|
||||
}
|
||||
return user->maxGmLevel;
|
||||
}
|
||||
|
||||
// Helper function to check if user has minimum GM level
|
||||
bool HasMinimumGMLevel(const crow::request& req, DashboardApp& app, eGameMasterLevel required) {
|
||||
auto level = GetUserGMLevel(req, app);
|
||||
return static_cast<uint8_t>(level) >= static_cast<uint8_t>(required);
|
||||
}
|
||||
|
||||
void Setup(DashboardApp& app) {
|
||||
// Get all play keys (DataTables endpoint)
|
||||
CROW_ROUTE(app, "/api/playkeys")
|
||||
.methods("GET"_method)
|
||||
([&](const crow::request& req) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::LEAD_MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
crow::json::wvalue::list data;
|
||||
|
||||
try {
|
||||
auto keys = Database::Get()->GetAllPlayKeys();
|
||||
|
||||
for (const auto& key : keys) {
|
||||
crow::json::wvalue item;
|
||||
item["id"] = key.id;
|
||||
item["key_string"] = key.key_string;
|
||||
item["key_uses"] = key.key_uses;
|
||||
item["times_used"] = key.times_used;
|
||||
item["active"] = key.active;
|
||||
item["notes"] = key.notes;
|
||||
item["created_at"] = static_cast<uint64_t>(key.created_at);
|
||||
|
||||
data.push_back(std::move(item));
|
||||
}
|
||||
} catch (std::exception& ex) {
|
||||
// return empty list on failure
|
||||
}
|
||||
|
||||
response["data"] = std::move(data);
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Create a new play key
|
||||
CROW_ROUTE(app, "/api/playkeys/create")
|
||||
.methods("POST"_method)
|
||||
([&](const crow::request& req) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::LEAD_MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
auto body = crow::json::load(req.body);
|
||||
if (!body) {
|
||||
return crow::response(400, "{\"error\": \"Invalid JSON\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
uint32_t count = body.has("count") ? body["count"].i() : 1;
|
||||
uint32_t uses = body.has("uses") ? body["uses"].i() : 1;
|
||||
std::string notes;
|
||||
if (body.has("notes"))
|
||||
notes = std::string(body["notes"].s());
|
||||
else
|
||||
notes = "";
|
||||
|
||||
// Limit to prevent abuse
|
||||
if (count > 100) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Cannot create more than 100 keys at once";
|
||||
return crow::response(response);
|
||||
}
|
||||
|
||||
crow::json::wvalue::list keys;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
std::string keyString = GeneratePlayKeyString();
|
||||
Database::Get()->CreatePlayKey(keyString, uses, notes);
|
||||
keys.push_back(keyString);
|
||||
}
|
||||
|
||||
response["success"] = true;
|
||||
response["keys"] = std::move(keys);
|
||||
response["count"] = count;
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Get single play key by ID
|
||||
CROW_ROUTE(app, "/api/playkeys/<int>")
|
||||
.methods("GET"_method)
|
||||
([&](const crow::request& req, int key_id) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::LEAD_MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
auto key = Database::Get()->GetPlayKeyById(key_id);
|
||||
if (!key) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Play key not found";
|
||||
return crow::response(404, response);
|
||||
}
|
||||
|
||||
response["success"] = true;
|
||||
response["id"] = key->id;
|
||||
response["key_string"] = key->key_string;
|
||||
response["key_uses"] = key->key_uses;
|
||||
response["times_used"] = key->times_used;
|
||||
response["active"] = key->active;
|
||||
response["notes"] = key->notes;
|
||||
response["created_at"] = static_cast<uint64_t>(key->created_at);
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Update a play key
|
||||
CROW_ROUTE(app, "/api/playkeys/<int>")
|
||||
.methods("PUT"_method, "POST"_method)
|
||||
([&](const crow::request& req, int key_id) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::LEAD_MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
auto body = crow::json::load(req.body);
|
||||
if (!body) {
|
||||
return crow::response(400, "{\"error\": \"Invalid JSON\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
// Get current key info
|
||||
auto key = Database::Get()->GetPlayKeyById(key_id);
|
||||
if (!key) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Play key not found";
|
||||
return crow::response(404, response);
|
||||
}
|
||||
|
||||
uint32_t uses = body.has("uses") ? body["uses"].i() : key->key_uses;
|
||||
bool active = body.has("active") ? body["active"].b() : key->active;
|
||||
std::string notes;
|
||||
if (body.has("notes"))
|
||||
notes = std::string(body["notes"].s());
|
||||
else
|
||||
notes = key->notes;
|
||||
|
||||
Database::Get()->UpdatePlayKey(key_id, uses, active, notes);
|
||||
|
||||
response["success"] = true;
|
||||
response["message"] = "Play key updated successfully";
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Delete a play key
|
||||
CROW_ROUTE(app, "/api/playkeys/<int>")
|
||||
.methods("DELETE"_method)
|
||||
([&](const crow::request& req, int key_id) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::LEAD_MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
|
||||
try {
|
||||
// Check if key exists
|
||||
auto key = Database::Get()->GetPlayKeyById(key_id);
|
||||
if (!key) {
|
||||
response["success"] = false;
|
||||
response["error"] = "Play key not found";
|
||||
return crow::response(404, response);
|
||||
}
|
||||
|
||||
Database::Get()->DeletePlayKey(key_id);
|
||||
|
||||
response["success"] = true;
|
||||
response["message"] = "Play key deleted successfully";
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["success"] = false;
|
||||
response["error"] = ex.what();
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
|
||||
// Get accounts associated with a play key
|
||||
CROW_ROUTE(app, "/api/playkeys/<int>/accounts")
|
||||
.methods("GET"_method)
|
||||
([&](const crow::request& req, int key_id) {
|
||||
if (!HasMinimumGMLevel(req, app, eGameMasterLevel::LEAD_MODERATOR)) {
|
||||
return crow::response(403, "{\"error\": \"Forbidden\"}");
|
||||
}
|
||||
|
||||
crow::json::wvalue response;
|
||||
crow::json::wvalue::list accounts;
|
||||
|
||||
try {
|
||||
// Get all accounts and filter by play_key_id
|
||||
auto allAccounts = Database::Get()->GetAllAccounts();
|
||||
for (const auto& acct : allAccounts) {
|
||||
if (acct.play_key_id == static_cast<uint32_t>(key_id)) {
|
||||
crow::json::wvalue item;
|
||||
item["id"] = acct.id;
|
||||
item["name"] = acct.name;
|
||||
item["gm_level"] = static_cast<int>(acct.gm_level);
|
||||
item["banned"] = acct.banned;
|
||||
item["locked"] = acct.locked;
|
||||
|
||||
accounts.push_back(std::move(item));
|
||||
}
|
||||
}
|
||||
|
||||
response["data"] = std::move(accounts);
|
||||
|
||||
} catch (std::exception& ex) {
|
||||
response["error"] = ex.what();
|
||||
return crow::response(500, response);
|
||||
}
|
||||
|
||||
return crow::response(response);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace PlayKeysBlueprint
|
||||
20
dDashboardServer/blueprints/PlayKeysBlueprint.h
Normal file
20
dDashboardServer/blueprints/PlayKeysBlueprint.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef __PLAYKEYSBLUEPRINT_H__
|
||||
#define __PLAYKEYSBLUEPRINT_H__
|
||||
|
||||
#include "crow.h"
|
||||
#include "crow/middlewares/session.h"
|
||||
|
||||
namespace PlayKeysBlueprint {
|
||||
|
||||
using Session = crow::SessionMiddleware<crow::InMemoryStore>;
|
||||
using DashboardApp = crow::App<crow::CookieParser, Session>;
|
||||
|
||||
/**
|
||||
* Setup play keys management routes
|
||||
* Registers routes for creating, viewing, editing, and deleting play keys
|
||||
*/
|
||||
void Setup(DashboardApp& app);
|
||||
|
||||
} // namespace PlayKeysBlueprint
|
||||
|
||||
#endif // __PLAYKEYSBLUEPRINT_H__
|
||||
Reference in New Issue
Block a user