Files
retoor 3c4334cec9 feat: implement MoloApp plans 01-15 with integration
Full C++23 framework tree: runtime, observability, security,
data, app, realtime, supervisor, clients, CLI, example plus
enterprise tenancy, audit, operability, governance,
supplychain. Postgres via docker compose for tests.
2026-10-23 18:20:04 +02:00

225 lines
8.9 KiB
C++

#include "App.hpp"
#include "../fw/Jobs.hpp"
#include "../fw/Net.hpp"
#include "../fw/Observ.hpp"
#include "../fw/Router.hpp"
#include "../fw/Templates.hpp"
#include "../fw/Validate.hpp"
#include "../fw/Workflow.hpp"
namespace molo::example::app {
void register_ops_demo(fw::Router& router, Services& services) {
fw::EventBus* events = services.events.get();
fw::Metrics* metrics = services.metrics.get();
fw::Telemetry* telemetry = services.telemetry.get();
fw::Supervisor* supervisor = services.supervisor.get();
fw::TaskQueue* tasks = services.tasks.get();
fw::ReplicationLog* repl = services.repl.get();
fw::OpenApi* openapi = services.openapi.get();
fw::Environment* env = services.templates.get();
router.post("/events/fire", "events-fire", [events](fw::Request req) {
auto input = fw::parse_json_object_flat(req.body);
if (input.empty()) input = fw::parse_form(req.body);
std::string type = input.count("type") ? input["type"] : "demo.ping";
std::string payload =
input.count("payload") ? input["payload"] : "ping";
events->emit({type, payload, req.correlation_id});
return fw::JsonResponse(
"{\"fired\":\"" + fw::json_escape(type) + "\"}", 200);
});
router.get("/events/fire", "events-list", [events](fw::Request req) {
(void)req;
auto delivered = events->delivered();
std::string body = "{\"delivered\":[";
bool first = true;
for (const auto& e : delivered) {
if (!first) body += ",";
first = false;
body += "{\"type\":\"" + fw::json_escape(e.type) + "\",\"payload\":\"" +
fw::json_escape(e.payload) + "\"}";
}
return fw::JsonResponse(body + "]}", 200);
});
router.get("/metrics/counters", "metrics",
[metrics](fw::Request req) {
(void)req;
metrics->inc("example.page.views");
fw::Response res(200, metrics->render_prometheus(),
"text/plain; version=0.0.4");
return res;
});
router.get("/telemetry/trace", "telemetry",
[telemetry](fw::Request req) {
fw::Span span =
telemetry->start_span("demo", req.correlation_id);
telemetry->finish_span(span);
return fw::JsonResponse(
"{\"trace\":\"" + fw::json_escape(span.trace_id) +
"\",\"span\":\"" + fw::json_escape(span.span_id) +
"\",\"correlation\":\"" +
fw::json_escape(req.correlation_id) + "\"}",
200);
});
router.get("/replication/state", "repl", [repl](fw::Request req) {
unsigned long since = 0;
try {
since = (unsigned long)std::stoul(req.query_or("since", "0"));
} catch (...) {
since = 0;
}
auto entries = repl->since(since);
std::string body = "{\"latest\":" + std::to_string(repl->latest()) +
",\"entries\":[";
bool first = true;
for (const auto& e : entries) {
if (!first) body += ",";
first = false;
body += "{\"seq\":" + std::to_string(e.seq) + ",\"object\":\"" +
fw::json_escape(e.object) + "\",\"resolution\":\"" +
fw::json_escape(e.resolution) + "\"}";
}
return fw::JsonResponse(body + "]}", 200);
});
router.post("/replication/state", "repl-append", [repl](fw::Request req) {
auto input = fw::parse_json_object_flat(req.body);
if (input.empty()) input = fw::parse_form(req.body);
std::string object = input.count("object") ? input["object"] : "doc-1";
std::string data = input.count("data") ? input["data"] : "{}";
fw::ReplEntry e = repl->append(object, data);
return fw::JsonResponse("{\"seq\":" + std::to_string(e.seq) +
",\"resolution\":\"" +
fw::json_escape(e.resolution) + "\"}",
201);
});
router.get("/supervisor/tasks", "tasks-list", [tasks](fw::Request req) {
(void)req;
std::string body = "{\"tasks\":[";
bool first = true;
for (const auto& t : tasks->list()) {
if (!first) body += ",";
first = false;
body += "{\"id\":\"" + fw::json_escape(t.id) + "\",\"state\":\"" +
fw::json_escape(t.state) + "\"}";
}
return fw::JsonResponse(body + "]}", 200);
});
router.post("/supervisor/tasks", "tasks-enqueue", [tasks](fw::Request req) {
auto input = fw::parse_json_object_flat(req.body);
if (input.empty()) input = fw::parse_form(req.body);
std::string kind = input.count("kind") ? input["kind"] : "email";
std::string payload =
input.count("payload") ? input["payload"] : "{}";
std::string id = tasks->enqueue(kind, payload, 0);
if (id.empty()) return fw::JsonResponse("{\"error\":\"queue full\"}", 500);
return fw::JsonResponse("{\"id\":\"" + fw::json_escape(id) + "\"}", 201);
});
router.get("/supervisor/workers", "workers",
[supervisor](fw::Request req) {
(void)req;
std::string body = "{\"workers\":[";
bool first = true;
for (const auto& w : supervisor->status()) {
if (!first) body += ",";
first = false;
body += "{\"name\":\"" + fw::json_escape(w.name) +
"\",\"running\":" +
(w.running ? "true" : "false") + ",\"restarts\":" +
std::to_string(w.restarts) + "}";
}
return fw::JsonResponse(body + "]}", 200);
});
router.get("/restclient/self", "restclient", [](fw::Request req) {
(void)req;
fw::RestClient client([](fw::Request r) {
(void)r;
return fw::JsonResponse("{\"status\":\"ok\"}", 200);
});
fw::Response upstream = client.get("/health");
return fw::JsonResponse(
"{\"self_call\":" + std::to_string(upstream.status) + "}", 200);
});
openapi->add("/health", "get", "health probe");
openapi->add("/orm/users", "get", "list users");
openapi->add("/validate/user", "post", "validate user model");
router.get("/openapi.json", "openapi", [openapi](fw::Request req) {
(void)req;
return fw::Response(200, openapi->to_json(), "application/json");
});
router.get("/docs", "docs", [env, openapi](fw::Request req) {
(void)req;
(void)openapi;
fw::TemplateCtx ctx;
ctx["title"] = std::string("API docs");
fw::TemplateList rows;
rows.push_back({{"method", "GET"},
{"path", "/health"},
{"summary", "health probe"}});
rows.push_back({{"method", "GET"},
{"path", "/orm/users"},
{"summary", "list users"}});
rows.push_back({{"method", "POST"},
{"path", "/validate/user"},
{"summary", "validate user model"}});
ctx["routes"] = rows;
return fw::Response(200, env->render("docs.html", ctx),
"text/html; charset=utf-8");
});
router.get("/cli/ai", "ai", [](fw::Request req) {
std::string prompt = req.query_or("prompt", "add a route");
fw::AiSample sample = fw::ai_harness_sample(prompt);
std::string body = "{\"base_url\":\"" + sample.base_url +
"\",\"model\":\"" + sample.model +
"\",\"prompt\":\"" +
fw::json_escape(sample.redacted_prompt) +
"\",\"stream_events\":" +
std::to_string(sample.stream_chunks.size()) + "}";
return fw::JsonResponse(body, 200);
});
router.post("/workflow/release", "workflow", [](fw::Request req) {
auto input = fw::parse_json_object_flat(req.body);
if (input.empty()) input = fw::parse_form(req.body);
std::string subjects = input.count("commits") ? input["commits"] : "";
std::vector<fw::CommitInfo> commits;
std::size_t start = 0;
while (start < subjects.size()) {
std::size_t bar = subjects.find('|', start);
std::string one =
subjects.substr(start, bar == std::string::npos ? bar : bar - start);
fw::CommitInfo info;
std::string error;
if (!fw::parse_commit(one, "", info, error))
return fw::JsonResponse(
"{\"error\":\"bad commit: " + fw::json_escape(error) + "\"}",
400);
commits.push_back(info);
if (bar == std::string::npos) break;
start = bar + 1;
}
fw::ReleasePlanner planner;
fw::ChangelogWriter writer;
fw::ReleaseVerifier verifier;
verifier.add_check("suite", true, "example suite green");
verifier.add_check("live", true, "health probe ok");
std::string version = planner.next_version("0.1.0", commits);
std::string body =
"{\"bump\":\"" + fw::bump_name(planner.decide(commits)) +
"\",\"version\":\"" + version + "\",\"green\":" +
(verifier.green() ? "true" : "false") + ",\"changelog\":\"" +
fw::json_escape(writer.entry(version, "2026-10-23", commits)) + "\"}";
return fw::JsonResponse(body, 200);
});
}
} // namespace molo::example::app