|
@@ -0,0 +1,445 @@
|
|
|
|
|
+#include "workflow_group_controller.hpp"
|
|
|
|
|
+#include "common/uuid.hpp"
|
|
|
|
|
+#include "logging/logger.hpp"
|
|
|
|
|
+
|
|
|
|
|
+namespace smartbotic::webserver::api {
|
|
|
|
|
+
|
|
|
|
|
+using namespace common;
|
|
|
|
|
+
|
|
|
|
|
+WorkflowGroupController::WorkflowGroupController(storage::StorageClient& storage,
|
|
|
|
|
+ auth::AuthMiddleware& middleware,
|
|
|
|
|
+ WebSocketServer& ws_server)
|
|
|
|
|
+ : storage_(storage)
|
|
|
|
|
+ , middleware_(middleware)
|
|
|
|
|
+ , ws_server_(ws_server) {}
|
|
|
|
|
+
|
|
|
|
|
+void WorkflowGroupController::registerRoutes(httplib::Server& server) {
|
|
|
|
|
+ // List groups - GET /api/v1/workflow-groups?parentId=xxx
|
|
|
|
|
+ server.Get("/api/v1/workflow-groups", [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ middleware_.requireAuth(req, res, [this](auto& req, auto& res, auto& ctx) {
|
|
|
|
|
+ listGroups(req, res, ctx);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Get single group - GET /api/v1/workflow-groups/:id
|
|
|
|
|
+ server.Get(R"(/api/v1/workflow-groups/([^/]+))", [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ middleware_.requireAuth(req, res, [this](auto& req, auto& res, auto& ctx) {
|
|
|
|
|
+ getGroup(req, res, ctx);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Create group - POST /api/v1/workflow-groups
|
|
|
|
|
+ server.Post("/api/v1/workflow-groups", [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ middleware_.requireAuth(req, res, [this](auto& req, auto& res, auto& ctx) {
|
|
|
|
|
+ createGroup(req, res, ctx);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Update group - PUT /api/v1/workflow-groups/:id
|
|
|
|
|
+ server.Put(R"(/api/v1/workflow-groups/([^/]+))", [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ middleware_.requireAuth(req, res, [this](auto& req, auto& res, auto& ctx) {
|
|
|
|
|
+ updateGroup(req, res, ctx);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Delete group - DELETE /api/v1/workflow-groups/:id
|
|
|
|
|
+ server.Delete(R"(/api/v1/workflow-groups/([^/]+))", [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ middleware_.requireAuth(req, res, [this](auto& req, auto& res, auto& ctx) {
|
|
|
|
|
+ deleteGroup(req, res, ctx);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Get path to group (for breadcrumbs) - GET /api/v1/workflow-groups/:id/path
|
|
|
|
|
+ server.Get(R"(/api/v1/workflow-groups/([^/]+)/path)", [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ middleware_.requireAuth(req, res, [this](auto& req, auto& res, auto& ctx) {
|
|
|
|
|
+ getGroupPath(req, res, ctx);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Move group to new parent - POST /api/v1/workflow-groups/:id/move
|
|
|
|
|
+ server.Post(R"(/api/v1/workflow-groups/([^/]+)/move)", [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ middleware_.requireAuth(req, res, [this](auto& req, auto& res, auto& ctx) {
|
|
|
|
|
+ moveGroup(req, res, ctx);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void WorkflowGroupController::listGroups(const httplib::Request& req, httplib::Response& res,
|
|
|
|
|
+ const auth::AuthContext& ctx) {
|
|
|
|
|
+ storage::QueryOptions options;
|
|
|
|
|
+
|
|
|
|
|
+ // Filter by parent ID - "root" means top-level groups (no parent)
|
|
|
|
|
+ if (req.has_param("parentId")) {
|
|
|
|
|
+ std::string parent_id = req.get_param_value("parentId");
|
|
|
|
|
+ if (parent_id == "root" || parent_id.empty()) {
|
|
|
|
|
+ // For root-level groups, we need to find groups with no parentId or empty parentId
|
|
|
|
|
+ // Storage query doesn't support null checks directly, so we use empty string
|
|
|
|
|
+ options.filters.push_back({"parentId", ""});
|
|
|
|
|
+ } else {
|
|
|
|
|
+ options.filters.push_back({"parentId", parent_id});
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Pagination
|
|
|
|
|
+ int page = 1;
|
|
|
|
|
+ int page_size = 100; // Groups typically don't have many at one level
|
|
|
|
|
+ if (req.has_param("page")) {
|
|
|
|
|
+ page = std::stoi(req.get_param_value("page"));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (req.has_param("pageSize")) {
|
|
|
|
|
+ page_size = std::stoi(req.get_param_value("pageSize"));
|
|
|
|
|
+ }
|
|
|
|
|
+ options.page = page;
|
|
|
|
|
+ options.page_size = page_size;
|
|
|
|
|
+ options.sorts.push_back({"name", true}); // Sort by name ascending
|
|
|
|
|
+
|
|
|
|
|
+ auto result = storage_.query("workflow_groups", options);
|
|
|
|
|
+ if (result.failed()) {
|
|
|
|
|
+ sendError(res, result.error().message(), 500);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json response;
|
|
|
|
|
+ response["groups"] = result.value().documents;
|
|
|
|
|
+ response["total"] = result.value().total_count;
|
|
|
|
|
+ response["page"] = page;
|
|
|
|
|
+ response["pageSize"] = page_size;
|
|
|
|
|
+ response["hasMore"] = result.value().has_more;
|
|
|
|
|
+
|
|
|
|
|
+ sendJson(res, response);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void WorkflowGroupController::getGroup(const httplib::Request& req, httplib::Response& res,
|
|
|
|
|
+ const auth::AuthContext& ctx) {
|
|
|
|
|
+ std::string id = req.matches[1];
|
|
|
|
|
+
|
|
|
|
|
+ auto result = storage_.get("workflow_groups", id);
|
|
|
|
|
+ if (result.failed()) {
|
|
|
|
|
+ sendError(res, "Group not found", 404);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ sendJson(res, result.value());
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void WorkflowGroupController::createGroup(const httplib::Request& req, httplib::Response& res,
|
|
|
|
|
+ const auth::AuthContext& ctx) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ auto body = nlohmann::json::parse(req.body);
|
|
|
|
|
+
|
|
|
|
|
+ // Generate ID with wfg_ prefix
|
|
|
|
|
+ std::string id = UUID::generatePrefixed("wfg");
|
|
|
|
|
+
|
|
|
|
|
+ // Set owner
|
|
|
|
|
+ body["ownerId"] = ctx.user_id;
|
|
|
|
|
+
|
|
|
|
|
+ // Validate required fields
|
|
|
|
|
+ if (!body.contains("name") || body["name"].get<std::string>().empty()) {
|
|
|
|
|
+ sendError(res, "Name is required", 400);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Handle parentId - normalize "root" or missing to empty string
|
|
|
|
|
+ if (!body.contains("parentId") || body["parentId"].is_null() ||
|
|
|
|
|
+ body["parentId"].get<std::string>() == "root") {
|
|
|
|
|
+ body["parentId"] = "";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // If parentId is provided, verify it exists
|
|
|
|
|
+ std::string parent_id = body.value("parentId", "");
|
|
|
|
|
+ if (!parent_id.empty()) {
|
|
|
|
|
+ auto parent_result = storage_.get("workflow_groups", parent_id);
|
|
|
|
|
+ if (parent_result.failed()) {
|
|
|
|
|
+ sendError(res, "Parent group not found", 400);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ auto result = storage_.insert("workflow_groups", body, id);
|
|
|
|
|
+ if (result.failed()) {
|
|
|
|
|
+ sendError(res, result.error().message(), 500);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Get the created group with metadata
|
|
|
|
|
+ auto group = storage_.get("workflow_groups", id);
|
|
|
|
|
+ if (group.failed()) {
|
|
|
|
|
+ sendError(res, "Failed to retrieve created group", 500);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Broadcast to WebSocket clients
|
|
|
|
|
+ ws_server_.broadcast("workflow_groups.created", group.value());
|
|
|
|
|
+
|
|
|
|
|
+ LOG_INFO("Workflow group created: {} by user {}", id, ctx.user_id);
|
|
|
|
|
+ sendJson(res, group.value(), 201);
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ sendError(res, "Invalid request body", 400);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void WorkflowGroupController::updateGroup(const httplib::Request& req, httplib::Response& res,
|
|
|
|
|
+ const auth::AuthContext& ctx) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ std::string id = req.matches[1];
|
|
|
|
|
+ auto body = nlohmann::json::parse(req.body);
|
|
|
|
|
+
|
|
|
|
|
+ // Prevent updating metadata fields
|
|
|
|
|
+ body.erase("id");
|
|
|
|
|
+ body.erase("_id");
|
|
|
|
|
+ body.erase("ownerId");
|
|
|
|
|
+ body.erase("createdAt");
|
|
|
|
|
+ body.erase("_createdAt");
|
|
|
|
|
+ body.erase("updatedAt");
|
|
|
|
|
+ body.erase("_updatedAt");
|
|
|
|
|
+ body.erase("version");
|
|
|
|
|
+ body.erase("_version");
|
|
|
|
|
+ body.erase("parentId"); // Use move endpoint to change parent
|
|
|
|
|
+
|
|
|
|
|
+ auto result = storage_.update("workflow_groups", id, body, 0, true);
|
|
|
|
|
+ if (result.failed()) {
|
|
|
|
|
+ sendError(res, result.error().message(), 404);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Get updated group
|
|
|
|
|
+ auto group = storage_.get("workflow_groups", id);
|
|
|
|
|
+ if (group.ok()) {
|
|
|
|
|
+ ws_server_.broadcast("workflow_groups.updated", group.value());
|
|
|
|
|
+ sendJson(res, group.value());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ sendJson(res, {{"id", id}, {"version", result.value()}});
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ sendError(res, "Invalid request body", 400);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void WorkflowGroupController::deleteGroup(const httplib::Request& req, httplib::Response& res,
|
|
|
|
|
+ const auth::AuthContext& ctx) {
|
|
|
|
|
+ std::string id = req.matches[1];
|
|
|
|
|
+
|
|
|
|
|
+ // Check force parameter
|
|
|
|
|
+ bool force = req.has_param("force") && req.get_param_value("force") == "true";
|
|
|
|
|
+
|
|
|
|
|
+ // Check if group has children
|
|
|
|
|
+ if (!force && hasChildren(id)) {
|
|
|
|
|
+ sendError(res, "Group is not empty. Use force=true to delete anyway.", 400);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // If force delete, move all workflows in this group to ungrouped (remove groupId)
|
|
|
|
|
+ if (force) {
|
|
|
|
|
+ // Find all workflows in this group
|
|
|
|
|
+ storage::QueryOptions wf_options;
|
|
|
|
|
+ wf_options.filters.push_back({"groupId", id});
|
|
|
|
|
+ wf_options.page_size = 1000; // Get all
|
|
|
|
|
+
|
|
|
|
|
+ auto workflows_result = storage_.query("workflows", wf_options);
|
|
|
|
|
+ if (workflows_result.ok()) {
|
|
|
|
|
+ for (const auto& workflow : workflows_result.value().documents) {
|
|
|
|
|
+ std::string wf_id = workflow.value("_id", "");
|
|
|
|
|
+ if (!wf_id.empty()) {
|
|
|
|
|
+ storage_.update("workflows", wf_id, {{"groupId", ""}}, 0, true);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Find all subgroups and move them to parent or root
|
|
|
|
|
+ storage::QueryOptions sg_options;
|
|
|
|
|
+ sg_options.filters.push_back({"parentId", id});
|
|
|
|
|
+ sg_options.page_size = 1000;
|
|
|
|
|
+
|
|
|
|
|
+ // Get current group's parent
|
|
|
|
|
+ auto group_result = storage_.get("workflow_groups", id);
|
|
|
|
|
+ std::string new_parent_id = "";
|
|
|
|
|
+ if (group_result.ok()) {
|
|
|
|
|
+ new_parent_id = group_result.value().value("parentId", "");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ auto subgroups_result = storage_.query("workflow_groups", sg_options);
|
|
|
|
|
+ if (subgroups_result.ok()) {
|
|
|
|
|
+ for (const auto& subgroup : subgroups_result.value().documents) {
|
|
|
|
|
+ std::string sg_id = subgroup.value("_id", "");
|
|
|
|
|
+ if (!sg_id.empty()) {
|
|
|
|
|
+ storage_.update("workflow_groups", sg_id, {{"parentId", new_parent_id}}, 0, true);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ auto result = storage_.remove("workflow_groups", id);
|
|
|
|
|
+ if (result.failed()) {
|
|
|
|
|
+ sendError(res, "Group not found", 404);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ws_server_.broadcast("workflow_groups.deleted", {{"id", id}});
|
|
|
|
|
+
|
|
|
|
|
+ LOG_INFO("Workflow group deleted: {} by user {}", id, ctx.user_id);
|
|
|
|
|
+ sendJson(res, {{"success", true}});
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void WorkflowGroupController::getGroupPath(const httplib::Request& req, httplib::Response& res,
|
|
|
|
|
+ const auth::AuthContext& ctx) {
|
|
|
|
|
+ std::string id = req.matches[1];
|
|
|
|
|
+
|
|
|
|
|
+ // Verify group exists
|
|
|
|
|
+ auto result = storage_.get("workflow_groups", id);
|
|
|
|
|
+ if (result.failed()) {
|
|
|
|
|
+ sendError(res, "Group not found", 404);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Build path from root to this group
|
|
|
|
|
+ auto path = buildGroupPath(id);
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json response;
|
|
|
|
|
+ response["path"] = path;
|
|
|
|
|
+ sendJson(res, response);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void WorkflowGroupController::moveGroup(const httplib::Request& req, httplib::Response& res,
|
|
|
|
|
+ const auth::AuthContext& ctx) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ std::string id = req.matches[1];
|
|
|
|
|
+ auto body = nlohmann::json::parse(req.body);
|
|
|
|
|
+
|
|
|
|
|
+ // Get new parent ID - can be null/empty/"root" for top-level
|
|
|
|
|
+ std::string new_parent_id = "";
|
|
|
|
|
+ if (body.contains("parentId") && !body["parentId"].is_null()) {
|
|
|
|
|
+ new_parent_id = body["parentId"].get<std::string>();
|
|
|
|
|
+ if (new_parent_id == "root") {
|
|
|
|
|
+ new_parent_id = "";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Verify group exists
|
|
|
|
|
+ auto group_result = storage_.get("workflow_groups", id);
|
|
|
|
|
+ if (group_result.failed()) {
|
|
|
|
|
+ sendError(res, "Group not found", 404);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Cannot move to self
|
|
|
|
|
+ if (new_parent_id == id) {
|
|
|
|
|
+ sendError(res, "Cannot move group to itself", 400);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // If new parent specified, verify it exists
|
|
|
|
|
+ if (!new_parent_id.empty()) {
|
|
|
|
|
+ auto parent_result = storage_.get("workflow_groups", new_parent_id);
|
|
|
|
|
+ if (parent_result.failed()) {
|
|
|
|
|
+ sendError(res, "Target parent group not found", 400);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check for cycles - moving a group to one of its descendants
|
|
|
|
|
+ if (!new_parent_id.empty() && wouldCreateCycle(id, new_parent_id)) {
|
|
|
|
|
+ sendError(res, "Cannot move group to one of its descendants", 400);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Perform the move
|
|
|
|
|
+ auto result = storage_.update("workflow_groups", id, {{"parentId", new_parent_id}}, 0, true);
|
|
|
|
|
+ if (result.failed()) {
|
|
|
|
|
+ sendError(res, result.error().message(), 500);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Get updated group
|
|
|
|
|
+ auto group = storage_.get("workflow_groups", id);
|
|
|
|
|
+ if (group.ok()) {
|
|
|
|
|
+ ws_server_.broadcast("workflow_groups.moved", group.value());
|
|
|
|
|
+ sendJson(res, group.value());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ sendJson(res, {{"id", id}, {"parentId", new_parent_id}});
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ LOG_INFO("Workflow group moved: {} to parent {} by user {}", id,
|
|
|
|
|
+ new_parent_id.empty() ? "root" : new_parent_id, ctx.user_id);
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ sendError(res, "Invalid request body", 400);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+std::vector<nlohmann::json> WorkflowGroupController::buildGroupPath(const std::string& group_id) {
|
|
|
|
|
+ std::vector<nlohmann::json> path;
|
|
|
|
|
+ std::string current_id = group_id;
|
|
|
|
|
+
|
|
|
|
|
+ // Build path by traversing up to root (max 50 levels to prevent infinite loops)
|
|
|
|
|
+ int max_depth = 50;
|
|
|
|
|
+ while (!current_id.empty() && max_depth-- > 0) {
|
|
|
|
|
+ auto result = storage_.get("workflow_groups", current_id);
|
|
|
|
|
+ if (result.failed()) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ auto& group = result.value();
|
|
|
|
|
+ // Insert at beginning to build root-to-target order
|
|
|
|
|
+ path.insert(path.begin(), group);
|
|
|
|
|
+
|
|
|
|
|
+ current_id = group.value("parentId", "");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return path;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool WorkflowGroupController::wouldCreateCycle(const std::string& group_id, const std::string& new_parent_id) {
|
|
|
|
|
+ // Check if new_parent_id is a descendant of group_id
|
|
|
|
|
+ std::string current_id = new_parent_id;
|
|
|
|
|
+ int max_depth = 50;
|
|
|
|
|
+
|
|
|
|
|
+ while (!current_id.empty() && max_depth-- > 0) {
|
|
|
|
|
+ if (current_id == group_id) {
|
|
|
|
|
+ return true; // Found the group in the ancestry of new parent - would create cycle
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ auto result = storage_.get("workflow_groups", current_id);
|
|
|
|
|
+ if (result.failed()) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ current_id = result.value().value("parentId", "");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+bool WorkflowGroupController::hasChildren(const std::string& group_id) {
|
|
|
|
|
+ // Check for subgroups
|
|
|
|
|
+ storage::QueryOptions sg_options;
|
|
|
|
|
+ sg_options.filters.push_back({"parentId", group_id});
|
|
|
|
|
+ sg_options.page_size = 1;
|
|
|
|
|
+
|
|
|
|
|
+ auto subgroups = storage_.query("workflow_groups", sg_options);
|
|
|
|
|
+ if (subgroups.ok() && subgroups.value().total_count > 0) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check for workflows
|
|
|
|
|
+ storage::QueryOptions wf_options;
|
|
|
|
|
+ wf_options.filters.push_back({"groupId", group_id});
|
|
|
|
|
+ wf_options.page_size = 1;
|
|
|
|
|
+
|
|
|
|
|
+ auto workflows = storage_.query("workflows", wf_options);
|
|
|
|
|
+ if (workflows.ok() && workflows.value().total_count > 0) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void WorkflowGroupController::sendJson(httplib::Response& res, const nlohmann::json& data, int status) {
|
|
|
|
|
+ res.status = status;
|
|
|
|
|
+ res.set_content(data.dump(), "application/json");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void WorkflowGroupController::sendError(httplib::Response& res, const std::string& message, int status) {
|
|
|
|
|
+ res.status = status;
|
|
|
|
|
+ res.set_content(nlohmann::json{{"error", message}}.dump(), "application/json");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+} // namespace smartbotic::webserver::api
|