mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-23 13:12:49 +00:00
feat(dashboard): the UGC page replaces UGC Search
The UGC Search page and the UGC page searched the same things (owner, account, property, the name and description a player gave a model, LOT, UGC / blueprint id) with the same SQL. The UGC page's lists are now the one search: - models' List view has a Where column: placed on a property (with the name given there and a link to the model in the property's 3D view), in a mail or in the creator's inventories, or not found (/api/ugc?where=1, UgcLinks::Whereabouts as before) - owners link to their character and, with accounts_view, account - while searching, the kind buttons say how many models and how many cars and rockets match (/api/ugc "matches") - /ugc_search?q= redirects to /ugc?view=list&q=; the menu's UGC Search entry is now UGC, which opens the UGC page (it was only under Server Admin, for ugc_manage) - References lose their Find button (Where it is has the same) and name the inventory a build is in The ugc-search page and script are removed; /api/ugc_links/search stays for API users. Check: the menu's UGC entry; an old /ugc_search?q= link opens the UGC list with the search filled in; owner:, account:, property:, name:, lot: and id: searches, and the counts on the kind buttons; the Where column's links (property, 3D, character, account). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include "UgcLinks.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
@@ -25,6 +26,17 @@ namespace {
|
||||
constexpr size_t MAX_INVENTORIES = 25;
|
||||
|
||||
|
||||
// A query value for an address: letters, digits and -._~ as they are, everything else %XX
|
||||
std::string UrlEncode(std::string_view value) {
|
||||
static constexpr char HEX[] = "0123456789ABCDEF";
|
||||
std::string out;
|
||||
for (const unsigned char c : value) {
|
||||
if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') out += static_cast<char>(c);
|
||||
else out += { '%', HEX[c >> 4], HEX[c & 15] };
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ---- Who may see a creation ----
|
||||
|
||||
// The property page's rule (as /api/properties/:id): properties_view, or the owner with own_properties
|
||||
@@ -128,9 +140,14 @@ namespace UgcLinks {
|
||||
}
|
||||
|
||||
void RegisterRoutes() {
|
||||
Route(eHTTPMethod::GET, "/ugc_search", Perm("properties_view"), "Find players' models, cars and rockets and where they are",
|
||||
// The UGC page's lists replaced the search page: its links (?q=) open them with the search filled in
|
||||
Route(eHTTPMethod::GET, "/ugc_search", Perm("properties_view"), "Moved: the UGC page's lists (/ugc?view=list&q=)",
|
||||
[](HTTPReply& reply, const HTTPContext& context) {
|
||||
RenderPage(reply, context, "ugc-search.jinja2", "ugc_search", { { "query", QueryValue(context.queryString, "q").substr(0, 100) } });
|
||||
const auto query = QueryValue(context.queryString, "q").substr(0, 100);
|
||||
reply.status = eHTTPStatusCode::FOUND;
|
||||
reply.location = "/ugc?view=list" + (query.empty() ? std::string() : "&q=" + UrlEncode(query));
|
||||
reply.message = "";
|
||||
reply.contentType = eContentType::TEXT_HTML;
|
||||
});
|
||||
|
||||
Route(eHTTPMethod::GET, "/api/ugc_links/search", Perm("properties_view"),
|
||||
|
||||
@@ -284,8 +284,8 @@ namespace UgcRoutes {
|
||||
"it). Query: q= (\"state:\", \"kind:\"/\"type:\" (a build type, e.g. build6), \"owner:\", \"account:\", \"property:\", \"name:\", "
|
||||
"\"lot:\"/\"module:\" (a LOT or a module's name), \"id:\", or plain text across names, owners and ids), state=, type=, sort=newest|oldest|owner|name|"
|
||||
"bricks|triangles|slowest|made|cpu|memory|savings (models) or newest|oldest|owner|name|modules|made|slowest|cpu|memory|references (assemblies), "
|
||||
"reverse=1 (the sort's other direction), page= (from 0), size= (1-200). {items, total, page, size, counts, totals, kinds, ugcPublicUrl, "
|
||||
"canManage}",
|
||||
"reverse=1 (the sort's other direction), page= (from 0), size= (1-200), where=1 (models: where each is, as an assembly's builds). {items, "
|
||||
"total, page, size, counts, totals, matches: {model, modular} (while searching: how many of each kind match), kinds, ugcPublicUrl, canManage}",
|
||||
[](HTTPReply& reply, const HTTPContext& context) {
|
||||
const bool modular = QueryValue(context.queryString, "kind") == "modular";
|
||||
auto parsed = ParseListSearch(QueryValue(context.queryString, "q").substr(0, 100));
|
||||
@@ -295,6 +295,7 @@ namespace UgcRoutes {
|
||||
const auto size = std::clamp(GeneralUtils::TryParse<uint32_t>(QueryValue(context.queryString, "size")).value_or(PAGE_SIZE), 1u, 200u);
|
||||
const auto sortText = QueryValue(context.queryString, "sort");
|
||||
const bool reverse = QueryValue(context.queryString, "reverse") == "1";
|
||||
const bool where = QueryValue(context.queryString, "where") == "1";
|
||||
const auto kinds = IconKinds();
|
||||
|
||||
// A page of models, and how many match
|
||||
@@ -345,10 +346,21 @@ namespace UgcRoutes {
|
||||
|
||||
nlohmann::json items = nlohmann::json::array();
|
||||
uint64_t total = 0;
|
||||
nlohmann::json matches = nullptr;
|
||||
const bool searching = !parsed.search.text.empty() || parsed.search.number.has_value();
|
||||
if (!modular) {
|
||||
const auto [entries, count] = models(page * size, size);
|
||||
total = count;
|
||||
for (const auto& entry : entries) items.push_back(EntryJson(entry));
|
||||
const auto whereabouts = where ? UgcLinks::Whereabouts(entries) : std::map<LWOOBJID, nlohmann::json>{};
|
||||
for (const auto& entry : entries) {
|
||||
auto item = EntryJson(entry);
|
||||
if (where) {
|
||||
const auto found = whereabouts.find(entry.id);
|
||||
item["where"] = found == whereabouts.end() ? nlohmann::json::array() : found->second;
|
||||
}
|
||||
items.push_back(std::move(item));
|
||||
}
|
||||
if (searching) matches = { { "model", total }, { "modular", assemblies().size() } };
|
||||
} else {
|
||||
const auto list = assemblies();
|
||||
total = list.size();
|
||||
@@ -370,11 +382,12 @@ namespace UgcRoutes {
|
||||
{ "characterName", a.characterName }, { "accountId", a.accountId }, { "accountName", a.accountName }, { "processedAt", a.processedAt },
|
||||
{ "processMs", a.processMs }, { "processCpuMs", a.processCpuMs }, { "processMemoryKb", a.processMemoryKb } });
|
||||
}
|
||||
if (searching) matches = { { "model", models(0, 1).second }, { "modular", total } };
|
||||
}
|
||||
JsonSuccess(reply, { { "counts", { { "model", Counts(Database::Get()->GetUgcProcessCounts()) }, { "modular", Counts(Database::Get()->GetModularBuildProcessCounts()) } } },
|
||||
{ "totals", { { "model", Totals(Database::Get()->GetUgcProcessTotals(false)) }, { "modular", Totals(Database::Get()->GetUgcProcessTotals(true)) } } },
|
||||
{ "items", items }, { "total", total }, { "page", page }, { "size", size }, { "more", static_cast<uint64_t>(page + 1) * size < total }, { "kinds", kinds },
|
||||
{ "ugcPublicUrl", Game::config->GetValue("ugc_public_url") }, { "canManage", Can(context, "ugc_manage") } });
|
||||
{ "matches", matches }, { "ugcPublicUrl", Game::config->GetValue("ugc_public_url") }, { "canManage", Can(context, "ugc_manage") } });
|
||||
});
|
||||
|
||||
Route(eHTTPMethod::GET, "/api/ugc/assembly/builds", Perm("properties_view"),
|
||||
|
||||
Reference in New Issue
Block a user