MQTTSuite
Loading...
Searching...
No Matches
mqtt::lib::admin Namespace Reference

Classes

struct  AdminOptions
struct  ReloadResult

Typedefs

using ReloadCallback = std::function<ReloadResult(bool)>

Functions

express::Router makeMappingAdminRouter (ConfigApplication *configApplication, const AdminOptions &opt, ReloadCallback onDeploy)

Typedef Documentation

◆ ReloadCallback

using mqtt::lib::admin::ReloadCallback = std::function<ReloadResult(bool)>

Definition at line 75 of file MappingAdminRouter.h.

Function Documentation

◆ makeMappingAdminRouter()

express::Router mqtt::lib::admin::makeMappingAdminRouter ( ConfigApplication * configApplication,
const AdminOptions & opt,
ReloadCallback onDeploy = {} )

Definition at line 67 of file MappingAdminRouter.cpp.

67 {
68 express::Router api;
69
70 api.use(express::middleware::JsonMiddleware());
71 api.use(express::middleware::BasicAuthentication(opt.user, opt.pass, opt.realm));
72
73 // GET /schema
74 api.get("/schema", [] APPLICATION(req, res) {
75 res->status(200).send(MqttMapper::getSchema());
76 });
77
78 // GET /config
79 api.get("/config", [configApplication] APPLICATION(req, res) {
80 try {
81 res->status(200).json(configApplication->getMqttMapper()->getMapping());
82 } catch (const std::exception& e) {
83 res->status(500).json({{"error", "Failed to load configuration"}, {"details", e.what()}});
84 }
85 });
86
87 // PATCH /config
88 api.patch("/config", [configApplication] APPLICATION(req, res) {
89 try {
90 const std::string bodyStr(req->body.begin(), req->body.end());
91 nlohmann::json patchOps = nlohmann::json::parse(bodyStr);
92
93 nlohmann::json current = configApplication->getMqttMapper()->getMapping();
94 current = current.patch(patchOps);
95
96 JsonMappingReader::saveDraft(configApplication->getMappingFilename(), current);
97
98 res->status(200).json({{"status", "patched"}, {"path", configApplication->getMappingFilename()}});
99 } catch (const nlohmann::json::parse_error& e) {
100 res->status(400).json({{"error", "Invalid JSON body"}, {"details", e.what()}});
101 } catch (const std::exception& e) {
102 res->status(422).json({{"error", "Patch application failed"}, {"details", e.what()}});
103 }
104 });
105
106 // POST /config (replace full draft config)
107 api.post("/config", [configApplication] APPLICATION(req, res) {
108 try {
109 const std::string bodyStr(req->body.begin(), req->body.end());
110 nlohmann::json replacement = nlohmann::json::parse(bodyStr);
111
112 if (!replacement.is_object()) {
113 res->status(422).json({{"error", "Config replacement must be a JSON object"}});
114 return;
115 }
116
117 JsonMappingReader::saveDraft(configApplication->getMappingFilename(), replacement);
118
119 res->status(200).json({{"status", "replaced"}, {"path", configApplication->getMappingFilename()}});
120 } catch (const nlohmann::json::parse_error& e) {
121 res->status(400).json({{"error", "Invalid JSON body"}, {"details", e.what()}});
122 } catch (const std::exception& e) {
123 res->status(422).json({{"error", "Config replacement failed"}, {"details", e.what()}});
124 }
125 });
126
127 // POST /config/deploy
128 api.post("/config/deploy", [configApplication, onDeploy] APPLICATION(req, res) {
129 try {
130 nlohmann::json newMappingJson = JsonMappingReader::deployDraft(configApplication->getMappingFilename());
131
132 bool mustReconnect = configApplication->setMapping(newMappingJson); // throws in case of an error during loading
133 // or validation. This exeption is catched
134 // in the MappingAdminRouter
135 ReloadResult reloadResult;
136 if (onDeploy) {
137 reloadResult = onDeploy(mustReconnect);
138 }
139
140 res->status(200).json({{"status", "deploy-ack"},
141 {"reload_mode", reloadResult.mode},
142 {"instances", reloadResult.instances},
143 {"subscribed", reloadResult.subscribed},
144 {"unsubscribed", reloadResult.unsubscribed}});
145 } catch (const std::exception& e) {
146 res->status(500).json({{"error", "Deploy failed"}, {"details", e.what()}});
147 }
148 });
149
150 // POST /config/validate
151 api.post("/config/validate", [] APPLICATION(req, res) {
152 try {
153 const std::string bodyStr(req->body.begin(), req->body.end());
154 auto document = nlohmann::json::parse(bodyStr);
155
156 nlohmann::json_schema::basic_error_handler err;
157 MqttMapper::validate(document, err);
158
159 if (err) {
160 res->status(422).json({{"valid", false}, {"error", "Validation failed"}});
161 } else {
162 res->status(200).json({{"valid", true}});
163 }
164 } catch (const std::exception& e) {
165 res->status(400).json({{"error", "Validation exception"}, {"details", e.what()}});
166 }
167 });
168
169 // GET /config/validateDraft
170 api.get("/config/validateDraft", [configApplication] APPLICATION(req, res) {
171 try {
172 const std::string draftPath = JsonMappingReader::getDraftPath(configApplication->getMappingFilename());
173
174 if (!std::filesystem::exists(draftPath)) {
175 res->status(404).json({{"valid", false}, {"error", "No draft configuration available"}, {"path", draftPath}});
176 return;
177 }
178
179 std::ifstream draftFile(draftPath);
180 if (!draftFile) {
181 res->status(500).json({{"valid", false}, {"error", "Cannot open draft configuration"}, {"path", draftPath}});
182 return;
183 }
184
185 nlohmann::json draftDocument;
186 draftFile >> draftDocument;
187
188 nlohmann::json_schema::basic_error_handler err;
189 MqttMapper::validate(draftDocument, err);
190
191 if (err) {
192 res->status(422).json({{"valid", false}, {"error", "Draft validation failed"}, {"path", draftPath}});
193 } else {
194 res->status(200).json({{"valid", true}, {"path", draftPath}});
195 }
196 } catch (const std::exception& e) {
197 res->status(400).json({{"valid", false}, {"error", "Draft validation exception"}, {"details", e.what()}});
198 }
199 });
200
201 // POST /config/rollback
202 api.post("/config/rollback", [configApplication, onDeploy] APPLICATION(req, res) {
203 try {
204 const std::string bodyStr(req->body.begin(), req->body.end());
205 auto jsonBody = nlohmann::json::parse(bodyStr);
206
207 if (!jsonBody.contains("version_id")) {
208 res->status(400).json({{"error", "Missing version_id"}});
209 return;
210 }
211
212 std::string versionId = jsonBody["version_id"];
213
214 nlohmann::json rolledbackMappingJson = JsonMappingReader::rollbackTo(configApplication->getMappingFilename(), versionId);
215
216 bool mustReconnect = configApplication->setMapping(rolledbackMappingJson); // throws in case of an error during loading
217 // or validation. This exeption is catched
218 // in the MappingAdminRouter
219 ReloadResult reloadResult;
220 if (onDeploy) {
221 reloadResult = onDeploy(mustReconnect); // Trigger hot-reload
222 }
223
224 res->status(200).json({{"status", "deploy-ack"},
225 {"reload_mode", reloadResult.mode},
226 {"instances", reloadResult.instances},
227 {"subscribed", reloadResult.subscribed},
228 {"unsubscribed", reloadResult.unsubscribed}});
229 } catch (const std::exception& e) {
230 res->status(500).json({{"error", "Rollback failed"}, {"details", e.what()}});
231 }
232 });
233
234 // GET /config/history
235 api.get("/config/history", [configApplication] APPLICATION(req, res) {
236 try {
237 auto history = JsonMappingReader::getHistory(configApplication->getMappingFilename());
238 nlohmann::json list = nlohmann::json::array();
239 for (const auto& h : history) {
240 list.push_back({{"id", h.id}, {"comment", h.comment}, {"date", h.date}});
241 }
242 res->status(200).json(list);
243 } catch ([[maybe_unused]] const std::exception& e) {
244 res->status(500).json({{"error", "Failed to fetch history"}});
245 }
246 });
247
248 api.get("/", [] APPLICATION(req, res) {
249 res->redirect("/ui");
250 });
251
252 api.get("/ui", [] APPLICATION(req, res) {
253 res->redirect("/ui/index.html");
254 });
255
256 api.use("/ui",
257 express::middleware::StaticMiddleware("/home/voc/tmp/integrator/mqtt-integrator-ui/dist/mqtt-integrator-ui/browser"));
258
259 api.get("*", [] APPLICATION(req, res) {
260 res->redirect("/ui/index.html");
261 });
262
263 return api;
264 }
std::string getMappingFilename() const
const std::shared_ptr< MqttMapper > getMqttMapper() const
bool setMapping(const nlohmann::json &json)
static nlohmann::json deployDraft(const std::string &mapFilePath)
static nlohmann::json rollbackTo(const std::string &mapFilePath, const std::string &versionId)
static std::vector< VersionEntry > getHistory(const std::string &mapFilePath)
static std::string getDraftPath(const std::string &mapFilePath)
static void saveDraft(const std::string &mapFilePath, const nlohmann::json &content)
static const nlohmann::json validate(const nlohmann::json &json)
static const std::string & getSchema()

References mqtt::lib::JsonMappingReader::VersionEntry::comment, mqtt::lib::JsonMappingReader::VersionEntry::date, mqtt::lib::JsonMappingReader::deployDraft(), mqtt::lib::JsonMappingReader::getDraftPath(), mqtt::lib::JsonMappingReader::getHistory(), mqtt::lib::MqttMapper::getMapping(), mqtt::lib::ConfigApplication::getMappingFilename(), mqtt::lib::ConfigApplication::getMqttMapper(), mqtt::lib::MqttMapper::getSchema(), mqtt::lib::JsonMappingReader::VersionEntry::id, mqtt::lib::admin::ReloadResult::instances, mqtt::lib::admin::ReloadResult::mode, mqtt::lib::admin::AdminOptions::pass, mqtt::lib::admin::AdminOptions::realm, mqtt::lib::JsonMappingReader::rollbackTo(), mqtt::lib::JsonMappingReader::saveDraft(), mqtt::lib::ConfigApplication::setMapping(), mqtt::lib::admin::ReloadResult::subscribed, mqtt::lib::admin::ReloadResult::unsubscribed, mqtt::lib::admin::AdminOptions::user, and mqtt::lib::MqttMapper::validate().

Here is the call graph for this function: