feat: wire sse_client_cpp_core for live updates from /api/boards/issues/stream

This commit is contained in:
agent
2026-05-18 20:05:14 +02:00
parent 264c5939f3
commit 4f5e9f6fbe
16 changed files with 726 additions and 44 deletions
+26 -17
View File
@@ -94,12 +94,25 @@ fn_http::Response do_post_json(const std::string& url, const std::string& body,
} // namespace
bool health(const ClientConfig& cfg) {
auto r = do_get(cfg.base_url + "/health", cfg.timeout_ms);
// /health legacy tiene auth middleware → 500. Usar endpoint sync layer
// (issue 0119) sin auth como ping.
auto r = do_get(cfg.base_url + "/api/boards/issues/cards", cfg.timeout_ms);
return r.status >= 200 && r.status < 300;
}
static std::string status_to_column(const std::string& s) {
if (s == "pendiente" || s == "pending") return "backlog";
if (s == "en-curso" || s == "in-progress") return "doing";
if (s == "en-revision" || s == "review") return "review";
if (s == "done" || s == "completado") return "done";
if (s == "deferred") return "deferred";
return "backlog";
}
std::vector<Card> list_cards(const ClientConfig& cfg, std::string& err) {
auto r = do_get(cfg.base_url + "/api/cards", cfg.timeout_ms);
// Issue 0119 sync layer: cards = issues + flows. Aqui solo issues; flows
// viven en su propio tab/panel cuando se anada.
auto r = do_get(cfg.base_url + "/api/boards/issues/cards", cfg.timeout_ms);
if (r.status == 0) { err = "transport: " + r.error; return {}; }
if (r.status >= 400) { err = "http " + std::to_string(r.status); return {}; }
std::vector<Card> out;
@@ -108,9 +121,9 @@ std::vector<Card> list_cards(const ClientConfig& cfg, std::string& err) {
c.id = find_str_field(obj, "id");
c.title = find_str_field(obj, "title");
c.description = find_str_field(obj, "description");
c.column_id = find_str_field(obj, "column_id");
c.priority = find_str_field(obj, "priority");
c.status = find_str_field(obj, "status");
c.column_id = status_to_column(c.status);
c.priority = find_str_field(obj, "priority");
c.position = find_int_field(obj, "position");
c.due_date = find_int_field(obj, "due_date");
c.assignee = find_str_field(obj, "assignee");
@@ -119,19 +132,15 @@ std::vector<Card> list_cards(const ClientConfig& cfg, std::string& err) {
return out;
}
std::vector<Column> list_columns(const ClientConfig& cfg, std::string& err) {
auto r = do_get(cfg.base_url + "/api/columns", cfg.timeout_ms);
if (r.status == 0) { err = "transport: " + r.error; return {}; }
if (r.status >= 400) { err = "http " + std::to_string(r.status); return {}; }
std::vector<Column> out;
for (const auto& obj : split_objects(r.body)) {
Column c;
c.id = find_str_field(obj, "id");
c.name = find_str_field(obj, "name");
c.order = static_cast<int>(find_int_field(obj, "order"));
if (!c.id.empty()) out.push_back(c);
}
return out;
std::vector<Column> list_columns(const ClientConfig& /*cfg*/, std::string& /*err*/) {
// Columnas fijas derivadas de taxonomia (issue 0103).
return {
{"backlog", "Backlog", 0},
{"doing", "Doing", 1},
{"review", "Review", 2},
{"done", "Done", 3},
{"deferred", "Deferred", 4},
};
}
bool move_card(const ClientConfig& cfg, const std::string& card_id,