mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-02-27 13:19:48 +00:00
WIP working state
This commit is contained in:
@@ -17,30 +17,7 @@ void RegisterAPIRoutes() {
|
||||
.method = eHTTPMethod::GET,
|
||||
.middleware = { std::make_shared<RequireAuthMiddleware>(0) },
|
||||
.handle = [](HTTPReply& reply, const HTTPContext& context) {
|
||||
std::lock_guard<std::mutex> lock(ServerState::g_StatusMutex);
|
||||
|
||||
nlohmann::json response = {
|
||||
{"auth", {
|
||||
{"online", ServerState::g_AuthStatus.online},
|
||||
{"players", ServerState::g_AuthStatus.players},
|
||||
{"version", ServerState::g_AuthStatus.version}
|
||||
}},
|
||||
{"chat", {
|
||||
{"online", ServerState::g_ChatStatus.online},
|
||||
{"players", ServerState::g_ChatStatus.players}
|
||||
}},
|
||||
{"worlds", nlohmann::json::array()}
|
||||
};
|
||||
|
||||
for (const auto& world : ServerState::g_WorldInstances) {
|
||||
response["worlds"].push_back({
|
||||
{"mapID", world.mapID},
|
||||
{"instanceID", world.instanceID},
|
||||
{"cloneID", world.cloneID},
|
||||
{"players", world.players},
|
||||
{"isPrivate", world.isPrivate}
|
||||
});
|
||||
}
|
||||
nlohmann::json response = ServerState::GetServerStateJson();
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = response.dump();
|
||||
@@ -92,10 +69,375 @@ void RegisterAPIRoutes() {
|
||||
.method = eHTTPMethod::GET,
|
||||
.middleware = { std::make_shared<RequireAuthMiddleware>(0) },
|
||||
.handle = [](HTTPReply& reply, const HTTPContext& context) {
|
||||
nlohmann::json response = {{"count", 0}, {"note", "Not yet implemented"}};
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = response.dump();
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
try {
|
||||
const uint32_t count = Database::Get()->GetCharacterCount();
|
||||
nlohmann::json response = {{"count", count}};
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = response.dump();
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
} catch (std::exception& ex) {
|
||||
LOG("Error in /api/characters/count: %s", ex.what());
|
||||
reply.status = eHTTPStatusCode::INTERNAL_SERVER_ERROR;
|
||||
reply.message = "{\"error\":\"Database error\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/tables/accounts - Get accounts table data (DataTables.js format)
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = "/api/tables/accounts",
|
||||
.method = eHTTPMethod::POST,
|
||||
.middleware = { std::make_shared<RequireAuthMiddleware>(0) },
|
||||
.handle = [](HTTPReply& reply, const HTTPContext& context) {
|
||||
try {
|
||||
// Only admins (GM > 0) can access table data
|
||||
if (context.gmLevel == 0) {
|
||||
reply.status = eHTTPStatusCode::FORBIDDEN;
|
||||
reply.message = "{\"error\":\"Forbidden - Admin access required\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json requestData = nlohmann::json::parse(context.body);
|
||||
|
||||
// Extract DataTables parameters
|
||||
uint32_t draw = requestData.value("draw", 1);
|
||||
uint32_t start = requestData.value("start", 0);
|
||||
uint32_t length = requestData.value("length", 10);
|
||||
|
||||
// Extract search - it can be a string or an object with a "value" property
|
||||
std::string search = "";
|
||||
if (requestData.contains("search")) {
|
||||
if (requestData["search"].is_string()) {
|
||||
search = requestData["search"].get<std::string>();
|
||||
} else if (requestData["search"].is_object() && requestData["search"].contains("value")) {
|
||||
search = requestData["search"]["value"].get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t orderColumn = 0;
|
||||
bool orderAsc = true;
|
||||
|
||||
// Extract order parameters
|
||||
if (requestData.contains("order") && requestData["order"].is_array() && requestData["order"].size() > 0) {
|
||||
orderColumn = requestData["order"][0].value("column", 0);
|
||||
orderAsc = requestData["order"][0].value("dir", "asc") == "asc";
|
||||
}
|
||||
|
||||
// Get the accounts table data
|
||||
nlohmann::json response = Database::Get()->GetAccountsTable(start, length, search, orderColumn, orderAsc);
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = response.dump();
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
} catch (const nlohmann::json::exception& jsonEx) {
|
||||
LOG("JSON error in /api/tables/accounts: %s", jsonEx.what());
|
||||
reply.status = eHTTPStatusCode::BAD_REQUEST;
|
||||
reply.message = "{\"error\":\"Invalid JSON\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
} catch (std::exception& ex) {
|
||||
LOG("Error in /api/tables/accounts: %s", ex.what());
|
||||
reply.status = eHTTPStatusCode::INTERNAL_SERVER_ERROR;
|
||||
reply.message = "{\"error\":\"Database error\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/tables/characters - Get characters table data (DataTables.js format)
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = "/api/tables/characters",
|
||||
.method = eHTTPMethod::POST,
|
||||
.middleware = { std::make_shared<RequireAuthMiddleware>(0) },
|
||||
.handle = [](HTTPReply& reply, const HTTPContext& context) {
|
||||
try {
|
||||
// Only admins (GM > 0) can access table data
|
||||
if (context.gmLevel == 0) {
|
||||
reply.status = eHTTPStatusCode::FORBIDDEN;
|
||||
reply.message = "{\"error\":\"Forbidden - Admin access required\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json requestData = nlohmann::json::parse(context.body);
|
||||
|
||||
uint32_t draw = requestData.value("draw", 1);
|
||||
uint32_t start = requestData.value("start", 0);
|
||||
uint32_t length = requestData.value("length", 10);
|
||||
|
||||
std::string search = "";
|
||||
if (requestData.contains("search")) {
|
||||
if (requestData["search"].is_string()) {
|
||||
search = requestData["search"].get<std::string>();
|
||||
} else if (requestData["search"].is_object() && requestData["search"].contains("value")) {
|
||||
search = requestData["search"]["value"].get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t orderColumn = 0;
|
||||
bool orderAsc = true;
|
||||
|
||||
if (requestData.contains("order") && requestData["order"].is_array() && requestData["order"].size() > 0) {
|
||||
orderColumn = requestData["order"][0].value("column", 0);
|
||||
orderAsc = requestData["order"][0].value("dir", "asc") == "asc";
|
||||
}
|
||||
|
||||
std::string tableData = Database::Get()->GetCharactersTable(start, length, search, orderColumn, orderAsc);
|
||||
|
||||
nlohmann::json response = nlohmann::json::parse(tableData);
|
||||
response["draw"] = draw;
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = response.dump();
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
} catch (const nlohmann::json::exception& jsonEx) {
|
||||
LOG("JSON error in /api/tables/characters: %s", jsonEx.what());
|
||||
reply.status = eHTTPStatusCode::BAD_REQUEST;
|
||||
reply.message = "{\"error\":\"Invalid JSON\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
} catch (std::exception& ex) {
|
||||
LOG("Error in /api/tables/characters: %s", ex.what());
|
||||
reply.status = eHTTPStatusCode::INTERNAL_SERVER_ERROR;
|
||||
reply.message = "{\"error\":\"Database error\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/tables/play_keys - Get play keys table data (DataTables.js format)
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = "/api/tables/play_keys",
|
||||
.method = eHTTPMethod::POST,
|
||||
.middleware = { std::make_shared<RequireAuthMiddleware>(0) },
|
||||
.handle = [](HTTPReply& reply, const HTTPContext& context) {
|
||||
try { // Only admins (GM > 0) can access table data
|
||||
if (context.gmLevel == 0) {
|
||||
reply.status = eHTTPStatusCode::FORBIDDEN;
|
||||
reply.message = "{\"error\":\"Forbidden - Admin access required\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
return;
|
||||
}
|
||||
nlohmann::json requestData = nlohmann::json::parse(context.body);
|
||||
|
||||
uint32_t draw = requestData.value("draw", 1);
|
||||
uint32_t start = requestData.value("start", 0);
|
||||
uint32_t length = requestData.value("length", 10);
|
||||
|
||||
std::string search = "";
|
||||
if (requestData.contains("search")) {
|
||||
if (requestData["search"].is_string()) {
|
||||
search = requestData["search"].get<std::string>();
|
||||
} else if (requestData["search"].is_object() && requestData["search"].contains("value")) {
|
||||
search = requestData["search"]["value"].get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t orderColumn = 0;
|
||||
bool orderAsc = true;
|
||||
|
||||
if (requestData.contains("order") && requestData["order"].is_array() && requestData["order"].size() > 0) {
|
||||
orderColumn = requestData["order"][0].value("column", 0);
|
||||
orderAsc = requestData["order"][0].value("dir", "asc") == "asc";
|
||||
}
|
||||
|
||||
std::string tableData = Database::Get()->GetPlayKeysTable(start, length, search, orderColumn, orderAsc);
|
||||
|
||||
nlohmann::json response = nlohmann::json::parse(tableData);
|
||||
response["draw"] = draw;
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = response.dump();
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
} catch (const nlohmann::json::exception& jsonEx) {
|
||||
LOG("JSON error in /api/tables/play_keys: %s", jsonEx.what());
|
||||
reply.status = eHTTPStatusCode::BAD_REQUEST;
|
||||
reply.message = "{\"error\":\"Invalid JSON\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
} catch (std::exception& ex) {
|
||||
LOG("Error in /api/tables/play_keys: %s", ex.what());
|
||||
reply.status = eHTTPStatusCode::INTERNAL_SERVER_ERROR;
|
||||
reply.message = "{\"error\":\"Database error\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/tables/properties - Get properties table data (DataTables.js format)
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = "/api/tables/properties",
|
||||
.method = eHTTPMethod::POST,
|
||||
.middleware = { std::make_shared<RequireAuthMiddleware>(0) },
|
||||
.handle = [](HTTPReply& reply, const HTTPContext& context) {
|
||||
try {
|
||||
// Only admins (GM > 0) can access table data
|
||||
if (context.gmLevel == 0) {
|
||||
reply.status = eHTTPStatusCode::FORBIDDEN;
|
||||
reply.message = "{\"error\":\"Forbidden - Admin access required\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json requestData = nlohmann::json::parse(context.body);
|
||||
|
||||
uint32_t draw = requestData.value("draw", 1);
|
||||
uint32_t start = requestData.value("start", 0);
|
||||
uint32_t length = requestData.value("length", 10);
|
||||
|
||||
std::string search = "";
|
||||
if (requestData.contains("search")) {
|
||||
if (requestData["search"].is_string()) {
|
||||
search = requestData["search"].get<std::string>();
|
||||
} else if (requestData["search"].is_object() && requestData["search"].contains("value")) {
|
||||
search = requestData["search"]["value"].get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t orderColumn = 0;
|
||||
bool orderAsc = true;
|
||||
|
||||
if (requestData.contains("order") && requestData["order"].is_array() && requestData["order"].size() > 0) {
|
||||
orderColumn = requestData["order"][0].value("column", 0);
|
||||
orderAsc = requestData["order"][0].value("dir", "asc") == "asc";
|
||||
}
|
||||
|
||||
std::string tableData = Database::Get()->GetPropertiesTable(start, length, search, orderColumn, orderAsc);
|
||||
|
||||
nlohmann::json response = nlohmann::json::parse(tableData);
|
||||
response["draw"] = draw;
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = response.dump();
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
} catch (const nlohmann::json::exception& jsonEx) {
|
||||
LOG("JSON error in /api/tables/properties: %s", jsonEx.what());
|
||||
reply.status = eHTTPStatusCode::BAD_REQUEST;
|
||||
reply.message = "{\"error\":\"Invalid JSON\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
} catch (std::exception& ex) {
|
||||
LOG("Error in /api/tables/properties: %s", ex.what());
|
||||
reply.status = eHTTPStatusCode::INTERNAL_SERVER_ERROR;
|
||||
reply.message = "{\"error\":\"Database error\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/tables/bug_reports - Get bug reports table data (DataTables.js format)
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = "/api/tables/bug_reports",
|
||||
.method = eHTTPMethod::POST,
|
||||
.middleware = { std::make_shared<RequireAuthMiddleware>(0) },
|
||||
.handle = [](HTTPReply& reply, const HTTPContext& context) {
|
||||
try { // Only admins (GM > 0) can access table data
|
||||
if (context.gmLevel == 0) {
|
||||
reply.status = eHTTPStatusCode::FORBIDDEN;
|
||||
reply.message = "{\"error\":\"Forbidden - Admin access required\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
return;
|
||||
}
|
||||
nlohmann::json requestData = nlohmann::json::parse(context.body);
|
||||
|
||||
uint32_t draw = requestData.value("draw", 1);
|
||||
uint32_t start = requestData.value("start", 0);
|
||||
uint32_t length = requestData.value("length", 10);
|
||||
|
||||
std::string search = "";
|
||||
if (requestData.contains("search")) {
|
||||
if (requestData["search"].is_string()) {
|
||||
search = requestData["search"].get<std::string>();
|
||||
} else if (requestData["search"].is_object() && requestData["search"].contains("value")) {
|
||||
search = requestData["search"]["value"].get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t orderColumn = 0;
|
||||
bool orderAsc = true;
|
||||
|
||||
if (requestData.contains("order") && requestData["order"].is_array() && requestData["order"].size() > 0) {
|
||||
orderColumn = requestData["order"][0].value("column", 0);
|
||||
orderAsc = requestData["order"][0].value("dir", "asc") == "asc";
|
||||
}
|
||||
|
||||
std::string tableData = Database::Get()->GetBugReportsTable(start, length, search, orderColumn, orderAsc);
|
||||
|
||||
nlohmann::json response = nlohmann::json::parse(tableData);
|
||||
response["draw"] = draw;
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = response.dump();
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
} catch (const nlohmann::json::exception& jsonEx) {
|
||||
LOG("JSON error in /api/tables/bug_reports: %s", jsonEx.what());
|
||||
reply.status = eHTTPStatusCode::BAD_REQUEST;
|
||||
reply.message = "{\"error\":\"Invalid JSON\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
} catch (std::exception& ex) {
|
||||
LOG("Error in /api/tables/bug_reports: %s", ex.what());
|
||||
reply.status = eHTTPStatusCode::INTERNAL_SERVER_ERROR;
|
||||
reply.message = "{\"error\":\"Database error\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/accounts/:id - Get single account by ID
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = "/api/accounts/:id",
|
||||
.method = eHTTPMethod::GET,
|
||||
.middleware = { std::make_shared<RequireAuthMiddleware>(0) },
|
||||
.handle = [](HTTPReply& reply, const HTTPContext& context) {
|
||||
try {
|
||||
// Extract account ID from URL path
|
||||
const std::string path = context.path;
|
||||
size_t lastSlash = path.rfind('/');
|
||||
if (lastSlash == std::string::npos) {
|
||||
reply.status = eHTTPStatusCode::BAD_REQUEST;
|
||||
reply.message = "{\"error\":\"Invalid account ID\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string idStr = path.substr(lastSlash + 1);
|
||||
uint32_t accountId = 0;
|
||||
try {
|
||||
accountId = std::stoul(idStr);
|
||||
} catch (...) {
|
||||
reply.status = eHTTPStatusCode::BAD_REQUEST;
|
||||
reply.message = "{\"error\":\"Invalid account ID\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
return;
|
||||
}
|
||||
|
||||
// Permission check: GM 0 can only view own account, GM > 0 can view any account
|
||||
if (context.gmLevel == 0) {
|
||||
// Regular user - get their own account ID
|
||||
auto currentUserInfo = Database::Get()->GetAccountInfo(context.authenticatedUser);
|
||||
if (!currentUserInfo.has_value() || currentUserInfo->id != accountId) {
|
||||
reply.status = eHTTPStatusCode::FORBIDDEN;
|
||||
reply.message = "{\"error\":\"Forbidden - You do not have permission to view this account\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get account data
|
||||
nlohmann::json response = Database::Get()->GetAccountById(accountId);
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = response.dump();
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
} catch (const nlohmann::json::exception& jsonEx) {
|
||||
LOG("JSON error in /api/accounts/:id: %s", jsonEx.what());
|
||||
reply.status = eHTTPStatusCode::BAD_REQUEST;
|
||||
reply.message = "{\"error\":\"Invalid JSON\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
} catch (std::exception& ex) {
|
||||
LOG("Error in /api/accounts/:id: %s", ex.what());
|
||||
reply.status = eHTTPStatusCode::INTERNAL_SERVER_ERROR;
|
||||
reply.message = "{\"error\":\"Database error\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
30
dDashboardServer/routes/CMakeLists.txt
Normal file
30
dDashboardServer/routes/CMakeLists.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
set(DASHBOARDROUTES_SOURCES
|
||||
"APIRoutes.cpp"
|
||||
"StaticRoutes.cpp"
|
||||
"DashboardRoutes.cpp"
|
||||
"WSRoutes.cpp"
|
||||
"AuthRoutes.cpp"
|
||||
)
|
||||
|
||||
add_library(DashboardRoutes STATIC ${DASHBOARDROUTES_SOURCES})
|
||||
|
||||
target_include_directories(DashboardRoutes PRIVATE
|
||||
"${PROJECT_SOURCE_DIR}/dCommon"
|
||||
"${PROJECT_SOURCE_DIR}/dCommon/dClient"
|
||||
"${PROJECT_SOURCE_DIR}/dCommon/dEnums"
|
||||
"${PROJECT_SOURCE_DIR}/dDatabase"
|
||||
"${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase"
|
||||
"${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/CDClientTables"
|
||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase"
|
||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables"
|
||||
"${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/MySQL"
|
||||
"${PROJECT_SOURCE_DIR}/dNet"
|
||||
"${PROJECT_SOURCE_DIR}/dWeb"
|
||||
"${PROJECT_SOURCE_DIR}/dServer"
|
||||
"${PROJECT_SOURCE_DIR}/thirdparty"
|
||||
"${PROJECT_SOURCE_DIR}/thirdparty/nlohmann"
|
||||
"${PROJECT_SOURCE_DIR}/dDashboardServer/auth"
|
||||
"${PROJECT_SOURCE_DIR}/dDashboardServer/routes"
|
||||
)
|
||||
|
||||
target_link_libraries(DashboardRoutes PRIVATE ${COMMON_LIBRARIES} dWeb dServer)
|
||||
@@ -25,35 +25,16 @@ void RegisterDashboardRoutes() {
|
||||
env.set_lstrip_blocks(true);
|
||||
|
||||
// Prepare data for template
|
||||
nlohmann::json data;
|
||||
// Get username from auth context
|
||||
data["username"] = context.authenticatedUser;
|
||||
data["gmLevel"] = context.gmLevel;
|
||||
nlohmann::json data = context.GetUserDataJson();
|
||||
|
||||
// Server status (placeholder data - will be updated with real data from master)
|
||||
data["auth"]["online"] = ServerState::g_AuthStatus.online;
|
||||
data["auth"]["players"] = ServerState::g_AuthStatus.players;
|
||||
data["chat"]["online"] = ServerState::g_ChatStatus.online;
|
||||
data["chat"]["players"] = ServerState::g_ChatStatus.players;
|
||||
|
||||
// World instances
|
||||
std::lock_guard<std::mutex> lock(ServerState::g_StatusMutex);
|
||||
data["worlds"] = nlohmann::json::array();
|
||||
for (const auto& world : ServerState::g_WorldInstances) {
|
||||
data["worlds"].push_back({
|
||||
{"mapID", world.mapID},
|
||||
{"instanceID", world.instanceID},
|
||||
{"cloneID", world.cloneID},
|
||||
{"players", world.players},
|
||||
{"isPrivate", world.isPrivate}
|
||||
});
|
||||
}
|
||||
// Server status - merge with server state
|
||||
nlohmann::json serverState = ServerState::GetServerStateJson();
|
||||
data.merge_patch(serverState);
|
||||
|
||||
// Statistics
|
||||
const uint32_t accountCount = Database::Get()->GetAccountCount();
|
||||
data["stats"]["onlinePlayers"] = 0; // TODO: Get from server communication
|
||||
data["stats"]["totalAccounts"] = accountCount;
|
||||
data["stats"]["totalCharacters"] = 0; // TODO: Add GetCharacterCount to database interface
|
||||
data["stats"]["totalAccounts"] = Database::Get()->GetAccountCount();
|
||||
data["stats"]["totalCharacters"] = Database::Get()->GetCharacterCount();
|
||||
|
||||
// Render template
|
||||
const std::string html = env.render_file("index.jinja2", data);
|
||||
@@ -82,9 +63,8 @@ void RegisterDashboardRoutes() {
|
||||
env.set_trim_blocks(true);
|
||||
env.set_lstrip_blocks(true);
|
||||
|
||||
// Render template with empty username
|
||||
nlohmann::json data;
|
||||
data["username"] = "";
|
||||
// Render template with empty user data (not authenticated)
|
||||
nlohmann::json data = context.GetUserDataJson();
|
||||
const std::string html = env.render_file("login.jinja2", data);
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
@@ -98,4 +78,214 @@ void RegisterDashboardRoutes() {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// GET /accounts/:id - View single account
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = "/accounts/:id",
|
||||
.method = eHTTPMethod::GET,
|
||||
.middleware = { std::make_shared<RequireAuthMiddleware>(0) },
|
||||
.handle = [](HTTPReply& reply, const HTTPContext& context) {
|
||||
try {
|
||||
// Extract account ID from URL path
|
||||
const std::string path = context.path;
|
||||
size_t lastSlash = path.rfind('/');
|
||||
if (lastSlash == std::string::npos) {
|
||||
reply.status = eHTTPStatusCode::NOT_FOUND;
|
||||
reply.message = "<h1>404 - Account not found</h1>";
|
||||
reply.contentType = eContentType::TEXT_HTML;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string idStr = path.substr(lastSlash + 1);
|
||||
uint32_t accountId = 0;
|
||||
try {
|
||||
accountId = std::stoul(idStr);
|
||||
} catch (...) {
|
||||
reply.status = eHTTPStatusCode::NOT_FOUND;
|
||||
reply.message = "<h1>404 - Invalid account ID</h1>";
|
||||
reply.contentType = eContentType::TEXT_HTML;
|
||||
return;
|
||||
}
|
||||
|
||||
// Permission check: GM 0 can only view own account, GM > 0 can view any account
|
||||
if (context.gmLevel == 0) {
|
||||
LOG("Regular user '%s' (GM level 0) is trying to access account ID %u", context.authenticatedUser.c_str(), accountId);
|
||||
// Regular user - get their own account ID
|
||||
auto currentUserInfo = Database::Get()->GetAccountInfo(context.authenticatedUser);
|
||||
if (!currentUserInfo.has_value() || currentUserInfo->id != accountId) {
|
||||
LOG("Permission denied: user '%s' cannot access account ID %u", context.authenticatedUser.c_str(), accountId);
|
||||
reply.status = eHTTPStatusCode::FORBIDDEN;
|
||||
reply.message = "<h1>403 - Forbidden</h1><p>You do not have permission to view this account.</p>";
|
||||
reply.contentType = eContentType::TEXT_HTML;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get account data from API
|
||||
nlohmann::json account = Database::Get()->GetAccountById(accountId);
|
||||
|
||||
// Check if account was found
|
||||
if (account.contains("error")) {
|
||||
reply.status = eHTTPStatusCode::NOT_FOUND;
|
||||
reply.message = "<h1>404 - Account not found</h1>";
|
||||
reply.contentType = eContentType::TEXT_HTML;
|
||||
return;
|
||||
}
|
||||
// Initialize inja environment
|
||||
inja::Environment env{"dDashboardServer/templates/"};
|
||||
env.set_trim_blocks(true);
|
||||
env.set_lstrip_blocks(true);
|
||||
|
||||
// Prepare data for template
|
||||
nlohmann::json data = context.GetUserDataJson();
|
||||
data["account"] = account;
|
||||
|
||||
// Render template
|
||||
const std::string html = env.render_file("account-view.jinja2", data);
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = html;
|
||||
reply.contentType = eContentType::TEXT_HTML;
|
||||
} catch (const std::exception& ex) {
|
||||
LOG("Error rendering account view template: %s", ex.what());
|
||||
reply.status = eHTTPStatusCode::INTERNAL_SERVER_ERROR;
|
||||
reply.message = "<h1>500 - Server Error</h1>";
|
||||
reply.contentType = eContentType::TEXT_HTML;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// GET /accounts - Accounts management page
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = "/accounts",
|
||||
.method = eHTTPMethod::GET,
|
||||
.middleware = { std::make_shared<RequireAuthMiddleware>(1) },
|
||||
.handle = [](HTTPReply& reply, const HTTPContext& context) {
|
||||
try {
|
||||
// Initialize inja environment
|
||||
inja::Environment env{"dDashboardServer/templates/"};
|
||||
env.set_trim_blocks(true);
|
||||
env.set_lstrip_blocks(true);
|
||||
|
||||
// Prepare data for template
|
||||
nlohmann::json data = context.GetUserDataJson();
|
||||
|
||||
// Render template
|
||||
const std::string html = env.render_file("accounts.jinja2", data);
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = html;
|
||||
reply.contentType = eContentType::TEXT_HTML;
|
||||
} catch (const std::exception& ex) {
|
||||
LOG("Error rendering accounts template: %s", ex.what());
|
||||
reply.status = eHTTPStatusCode::INTERNAL_SERVER_ERROR;
|
||||
reply.message = "{\"error\":\"Failed to render accounts page\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// GET /characters - Characters management page
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = "/characters",
|
||||
.method = eHTTPMethod::GET,
|
||||
.middleware = { std::make_shared<RequireAuthMiddleware>(1) },
|
||||
.handle = [](HTTPReply& reply, const HTTPContext& context) {
|
||||
try {
|
||||
inja::Environment env{"dDashboardServer/templates/"};
|
||||
env.set_trim_blocks(true);
|
||||
env.set_lstrip_blocks(true);
|
||||
|
||||
nlohmann::json data = context.GetUserDataJson();
|
||||
const std::string html = env.render_file("characters.jinja2", data);
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = html;
|
||||
reply.contentType = eContentType::TEXT_HTML;
|
||||
} catch (const std::exception& ex) {
|
||||
LOG("Error rendering characters template: %s", ex.what());
|
||||
reply.status = eHTTPStatusCode::INTERNAL_SERVER_ERROR;
|
||||
reply.message = "{\"error\":\"Failed to render characters page\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// GET /play_keys - Play keys management page
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = "/play_keys",
|
||||
.method = eHTTPMethod::GET,
|
||||
.middleware = { std::make_shared<RequireAuthMiddleware>(1) },
|
||||
.handle = [](HTTPReply& reply, const HTTPContext& context) {
|
||||
try {
|
||||
inja::Environment env{"dDashboardServer/templates/"};
|
||||
env.set_trim_blocks(true);
|
||||
env.set_lstrip_blocks(true);
|
||||
|
||||
nlohmann::json data = context.GetUserDataJson();
|
||||
const std::string html = env.render_file("play_keys.jinja2", data);
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = html;
|
||||
reply.contentType = eContentType::TEXT_HTML;
|
||||
} catch (const std::exception& ex) {
|
||||
LOG("Error rendering play_keys template: %s", ex.what());
|
||||
reply.status = eHTTPStatusCode::INTERNAL_SERVER_ERROR;
|
||||
reply.message = "{\"error\":\"Failed to render play_keys page\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// GET /properties - Properties management page
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = "/properties",
|
||||
.method = eHTTPMethod::GET,
|
||||
.middleware = { std::make_shared<RequireAuthMiddleware>(1) },
|
||||
.handle = [](HTTPReply& reply, const HTTPContext& context) {
|
||||
try {
|
||||
inja::Environment env{"dDashboardServer/templates/"};
|
||||
env.set_trim_blocks(true);
|
||||
env.set_lstrip_blocks(true);
|
||||
|
||||
nlohmann::json data = context.GetUserDataJson();
|
||||
const std::string html = env.render_file("properties.jinja2", data);
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = html;
|
||||
reply.contentType = eContentType::TEXT_HTML;
|
||||
} catch (const std::exception& ex) {
|
||||
LOG("Error rendering properties template: %s", ex.what());
|
||||
reply.status = eHTTPStatusCode::INTERNAL_SERVER_ERROR;
|
||||
reply.message = "{\"error\":\"Failed to render properties page\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// GET /bug_reports - Bug reports management page
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = "/bug_reports",
|
||||
.method = eHTTPMethod::GET,
|
||||
.middleware = { std::make_shared<RequireAuthMiddleware>(1) },
|
||||
.handle = [](HTTPReply& reply, const HTTPContext& context) {
|
||||
try {
|
||||
inja::Environment env{"dDashboardServer/templates/"};
|
||||
env.set_trim_blocks(true);
|
||||
env.set_lstrip_blocks(true);
|
||||
|
||||
nlohmann::json data = context.GetUserDataJson();
|
||||
const std::string html = env.render_file("bug_reports.jinja2", data);
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = html;
|
||||
reply.contentType = eContentType::TEXT_HTML;
|
||||
} catch (const std::exception& ex) {
|
||||
LOG("Error rendering bug_reports template: %s", ex.what());
|
||||
reply.status = eHTTPStatusCode::INTERNAL_SERVER_ERROR;
|
||||
reply.message = "{\"error\":\"Failed to render bug_reports page\"}";
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
class HTTPContext;
|
||||
|
||||
void RegisterDashboardRoutes();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include "json.hpp"
|
||||
|
||||
struct ServerStatus {
|
||||
bool online{false};
|
||||
@@ -27,5 +27,26 @@ namespace ServerState {
|
||||
extern ServerStatus g_AuthStatus;
|
||||
extern ServerStatus g_ChatStatus;
|
||||
extern std::vector<WorldInstanceInfo> g_WorldInstances;
|
||||
extern std::mutex g_StatusMutex;
|
||||
|
||||
// Helper function to get all server state as JSON
|
||||
inline nlohmann::json GetServerStateJson() {
|
||||
nlohmann::json data;
|
||||
data["auth"]["online"] = g_AuthStatus.online;
|
||||
data["auth"]["players"] = g_AuthStatus.players;
|
||||
data["chat"]["online"] = g_ChatStatus.online;
|
||||
data["chat"]["players"] = g_ChatStatus.players;
|
||||
|
||||
data["worlds"] = nlohmann::json::array();
|
||||
for (const auto& world : g_WorldInstances) {
|
||||
data["worlds"].push_back({
|
||||
{"mapID", world.mapID},
|
||||
{"instanceID", world.instanceID},
|
||||
{"cloneID", world.cloneID},
|
||||
{"players", world.players},
|
||||
{"isPrivate", world.isPrivate}
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,4 @@ void RegisterStaticRoutes() {
|
||||
// Serve JavaScript files
|
||||
ServeStaticFile("/js/dashboard.js", "dDashboardServer/static/js/dashboard.js");
|
||||
ServeStaticFile("/js/login.js", "dDashboardServer/static/js/login.js");
|
||||
|
||||
// Also serve from /static/ paths for backwards compatibility
|
||||
ServeStaticFile("/static/css/dashboard.css", "dDashboardServer/static/css/dashboard.css");
|
||||
ServeStaticFile("/static/js/dashboard.js", "dDashboardServer/static/js/dashboard.js");
|
||||
}
|
||||
|
||||
@@ -18,37 +18,14 @@ void RegisterWSRoutes() {
|
||||
}
|
||||
|
||||
void BroadcastDashboardUpdate() {
|
||||
std::lock_guard<std::mutex> lock(ServerState::g_StatusMutex);
|
||||
|
||||
nlohmann::json data = {
|
||||
{"auth", {
|
||||
{"online", ServerState::g_AuthStatus.online},
|
||||
{"players", ServerState::g_AuthStatus.players},
|
||||
{"version", ServerState::g_AuthStatus.version}
|
||||
}},
|
||||
{"chat", {
|
||||
{"online", ServerState::g_ChatStatus.online},
|
||||
{"players", ServerState::g_ChatStatus.players}
|
||||
}},
|
||||
{"worlds", nlohmann::json::array()}
|
||||
};
|
||||
|
||||
for (const auto& world : ServerState::g_WorldInstances) {
|
||||
data["worlds"].push_back({
|
||||
{"mapID", world.mapID},
|
||||
{"instanceID", world.instanceID},
|
||||
{"cloneID", world.cloneID},
|
||||
{"players", world.players},
|
||||
{"isPrivate", world.isPrivate}
|
||||
});
|
||||
}
|
||||
// Get server state data (auth, chat, worlds) - mutex is acquired internally
|
||||
nlohmann::json data = ServerState::GetServerStateJson();
|
||||
|
||||
// Add statistics
|
||||
try {
|
||||
const uint32_t accountCount = Database::Get()->GetAccountCount();
|
||||
data["stats"]["onlinePlayers"] = 0; // TODO: Get from server communication
|
||||
data["stats"]["totalAccounts"] = accountCount;
|
||||
data["stats"]["totalCharacters"] = 0; // TODO: Add GetCharacterCount to database interface
|
||||
data["stats"]["totalAccounts"] = Database::Get()->GetAccountCount();
|
||||
data["stats"]["totalCharacters"] = Database::Get()->GetCharacterCount();
|
||||
} catch (const std::exception& ex) {
|
||||
LOG_DEBUG("Error getting stats: %s", ex.what());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user