更新功能
This commit is contained in:
@@ -24,17 +24,18 @@ template <Described_Type T>
|
||||
requires std::copy_constructible<T> && std::assignable_from<T&, T>
|
||||
class Http_Resource {
|
||||
public:
|
||||
Http_Resource(T& value, std::string path) : value_(value), path_(std::move(path)) {}
|
||||
using Commit_Function = std::function<void(const T&)>;
|
||||
Http_Resource(T& value, std::string path, Commit_Function commit = {}, std::mutex* shared_mutex = nullptr) : value_(value), path_(std::move(path)), commit_(std::move(commit)), shared_mutex_(shared_mutex) {}
|
||||
Json amis_schema() const {
|
||||
std::scoped_lock lock(mutex_);
|
||||
return to_amis_form_schema(value_, path_ + "/data", "Confirm Changes");
|
||||
std::scoped_lock lock(resource_mutex());
|
||||
return to_amis_form_schema(value_, path_ + "/data", describe<T>().list_options().confirm_label);
|
||||
}
|
||||
void bind(httplib::Server& server) {
|
||||
server.Get(path_ + "/descriptor", [this](const httplib::Request&, httplib::Response& response) {
|
||||
write_http_json(response, Json{{"status", 0}, {"msg", ""}, {"data", to_descriptor_json<T>()}});
|
||||
});
|
||||
server.Get(path_ + "/data", [this](const httplib::Request&, httplib::Response& response) {
|
||||
std::scoped_lock lock(mutex_);
|
||||
std::scoped_lock lock(resource_mutex());
|
||||
write_http_json(response, Json{{"status", 0}, {"msg", ""}, {"data", to_frontend_json(value_)}});
|
||||
});
|
||||
server.Get(path_ + "/amis", [this](const httplib::Request&, httplib::Response& response) {
|
||||
@@ -43,12 +44,17 @@ public:
|
||||
server.Post(path_ + "/data", [this](const httplib::Request& request, httplib::Response& response) {
|
||||
try {
|
||||
const Json patch = Json::parse(request.body);
|
||||
std::scoped_lock lock(mutex_);
|
||||
const auto result = apply_frontend_patch(value_, patch);
|
||||
std::scoped_lock lock(resource_mutex());
|
||||
T candidate = value_;
|
||||
const auto result = apply_frontend_patch(candidate, patch);
|
||||
if(!result.success) {
|
||||
write_http_error(response, 422, result);
|
||||
return;
|
||||
}
|
||||
if(commit_) {
|
||||
commit_(candidate);
|
||||
}
|
||||
value_ = std::move(candidate);
|
||||
write_http_json(response, Json{{"status", 0}, {"msg", result.message}, {"data", to_frontend_json(value_)}});
|
||||
} catch(const std::exception& error) {
|
||||
response.status = 400;
|
||||
@@ -57,8 +63,13 @@ public:
|
||||
});
|
||||
}
|
||||
private:
|
||||
std::mutex& resource_mutex() const noexcept {
|
||||
return shared_mutex_ ? *shared_mutex_ : mutex_;
|
||||
}
|
||||
T& value_;
|
||||
std::string path_;
|
||||
Commit_Function commit_;
|
||||
std::mutex* shared_mutex_{};
|
||||
mutable std::mutex mutex_;
|
||||
};
|
||||
template <Described_Type T>
|
||||
@@ -134,16 +145,7 @@ public:
|
||||
for(const auto& entry : entries_) {
|
||||
ordered_entries.push_back(&entry);
|
||||
}
|
||||
if(request.has_param("orderBy")) {
|
||||
const std::string order_by = request.get_param_value("orderBy");
|
||||
if(is_sortable_field(order_by)) {
|
||||
const bool descending = request.has_param("orderDir") && request.get_param_value("orderDir") == "desc";
|
||||
std::stable_sort(ordered_entries.begin(), ordered_entries.end(), [&](const Entry* left, const Entry* right) {
|
||||
const int comparison = compare_json_values(to_frontend_json(left->value).at(order_by), to_frontend_json(right->value).at(order_by));
|
||||
return descending ? comparison > 0 : comparison < 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
apply_request_sort(request, ordered_entries);
|
||||
const std::size_t offset = std::min((page - 1) * per_page, ordered_entries.size());
|
||||
const std::size_t end = std::min(offset + per_page, ordered_entries.size());
|
||||
Json items = Json::array();
|
||||
@@ -180,6 +182,20 @@ public:
|
||||
write_http_json(response, Json{{"status", 400}, {"msg", error.what()}, {"data", Json::object()}});
|
||||
}
|
||||
});
|
||||
if(describe<T>().list_options().user_reorderable_) {
|
||||
server.Post(path_ + "/order", [this](const httplib::Request& request, httplib::Response& response) {
|
||||
try {
|
||||
const Json input = Json::parse(request.body);
|
||||
const auto ids = read_order_ids(input.at("ids"));
|
||||
std::scoped_lock lock(mutex_);
|
||||
reorder(ids);
|
||||
write_http_json(response, Json{{"status", 0}, {"msg", "reordered"}, {"data", Json::object()}});
|
||||
} catch(const std::exception& error) {
|
||||
response.status = 400;
|
||||
write_http_json(response, Json{{"status", 400}, {"msg", error.what()}, {"data", Json::object()}});
|
||||
}
|
||||
});
|
||||
}
|
||||
server.Put(item_pattern(), [this](const httplib::Request& request, httplib::Response& response) {
|
||||
update(request, response);
|
||||
});
|
||||
@@ -237,6 +253,9 @@ private:
|
||||
return left_value < right_value ? -1 : left_value > right_value ? 1 : 0;
|
||||
}
|
||||
static bool is_sortable_field(const std::string& name) {
|
||||
if(name == "id") {
|
||||
return true;
|
||||
}
|
||||
bool sortable{};
|
||||
std::apply([&](const auto&... field) {
|
||||
((field.name() == name ? sortable = field.is_sortable() : false), ...);
|
||||
@@ -252,6 +271,48 @@ private:
|
||||
}
|
||||
return id;
|
||||
}
|
||||
static std::vector<std::uint64_t> read_order_ids(const Json& value) {
|
||||
std::vector<std::uint64_t> result;
|
||||
if(value.is_array()) {
|
||||
for(const auto& item : value) {
|
||||
result.push_back(item.is_string() ? std::stoull(item.get<std::string>()) : item.get<std::uint64_t>());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
std::string text = value.get<std::string>();
|
||||
std::size_t begin{};
|
||||
while(begin < text.size()) {
|
||||
const auto end = text.find(',', begin);
|
||||
result.push_back(std::stoull(text.substr(begin, end == std::string::npos ? text.size() - begin : end - begin)));
|
||||
if(end == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
begin = end + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
void apply_request_sort(const httplib::Request& request, std::vector<const Entry*>& entries) const {
|
||||
const auto& options = describe<T>().list_options();
|
||||
std::string order_by = options.default_order_by;
|
||||
std::string order_dir = options.default_order_dir;
|
||||
if(request.has_param("orderBy")) {
|
||||
order_by = request.get_param_value("orderBy");
|
||||
order_dir = request.has_param("orderDir") ? request.get_param_value("orderDir") : "asc";
|
||||
}
|
||||
if(order_by.empty() || !is_sortable_field(order_by)) {
|
||||
return;
|
||||
}
|
||||
const bool descending = order_dir == "desc";
|
||||
std::stable_sort(entries.begin(), entries.end(), [&](const Entry* left, const Entry* right) {
|
||||
int comparison{};
|
||||
if(order_by == "id") {
|
||||
comparison = left->id < right->id ? -1 : left->id > right->id ? 1 : 0;
|
||||
} else {
|
||||
comparison = compare_json_values(to_frontend_json(left->value).at(order_by), to_frontend_json(right->value).at(order_by));
|
||||
}
|
||||
return descending ? comparison > 0 : comparison < 0;
|
||||
});
|
||||
}
|
||||
std::string item_pattern() const {
|
||||
return path_ + R"(/(\d+))";
|
||||
}
|
||||
@@ -272,6 +333,24 @@ private:
|
||||
response.status = 404;
|
||||
write_http_json(response, Json{{"status", 404}, {"msg", "record not found"}, {"data", Json::object()}});
|
||||
}
|
||||
void reorder(const std::vector<std::uint64_t>& ids) {
|
||||
std::vector<Entry> remaining = std::move(entries_);
|
||||
std::vector<Entry> reordered;
|
||||
reordered.reserve(remaining.size());
|
||||
for(const auto id : ids) {
|
||||
const auto iterator = std::find_if(remaining.begin(), remaining.end(), [id](const Entry& entry) {
|
||||
return entry.id == id;
|
||||
});
|
||||
if(iterator != remaining.end()) {
|
||||
reordered.push_back(std::move(*iterator));
|
||||
remaining.erase(iterator);
|
||||
}
|
||||
}
|
||||
for(auto& entry : remaining) {
|
||||
reordered.push_back(std::move(entry));
|
||||
}
|
||||
entries_ = std::move(reordered);
|
||||
}
|
||||
void update(const httplib::Request& request, httplib::Response& response) {
|
||||
try {
|
||||
Json patch = Json::parse(request.body);
|
||||
|
||||
Reference in New Issue
Block a user