Loading editor/html/index.html +11 −1 Changes for editor/html/index.html: 11 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -398,7 +398,7 @@ <div class="set-content sm-content"> <div id="sm-form-area" style="display:none"> <input type="hidden" id="sm-survey-id"> <div style="display:flex;gap:0.5em;align-items:flex-end;flex-wrap:wrap;margin-bottom:0.8em"> <div style="display:flex;gap:0.5em;align-items:flex-end;flex-wrap:wrap;margin-bottom:0.6em"> <div class="set-field" style="flex:1;min-width:140px;margin:0"> <label>Name (intern)</label> <input type="text" id="sm-survey-name" placeholder="kundenzufriedenheit"> Loading @@ -414,6 +414,16 @@ </div> </div> <!-- Publish to blogi --> <div style="display:flex;gap:0.4em;align-items:center;flex-wrap:wrap;margin-bottom:0.8em;padding:0.5em;background:var(--bg-tertiary);border-radius:6px;border:1px solid var(--border)"> <span style="font-size:0.8rem;color:var(--text-secondary);white-space:nowrap">🌐 Zu Blogi übertragen:</span> <select id="sm-publish-conn" style="flex:1;min-width:140px;font-size:0.8rem;padding:0.2em 0.4em;border-radius:4px;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text-primary)"> <option value="">-- Verbindung wählen --</option> </select> <button id="btn-sm-publish" class="set-save-btn" style="background:var(--accent);white-space:nowrap">↗ Publish</button> <span id="sm-publish-status" class="set-status"></span> </div> <div class="sm-editor-layout"> <!-- Canvas tree --> <div class="sm-canvas-wrap"> Loading editor/html/js/api.js +4 −0 Changes for editor/html/js/api.js: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -416,6 +416,10 @@ var EditorApi = (function() { deleteSurvey: function(id) { return request('DELETE', '/api/survey/delete/' + encodeURIComponent(id)); }, publishSurvey: function(surveyId, connId) { return request('POST', '/api/survey/publish', { survey_id: surveyId, conn_id: connId }); } }; })(); editor/html/js/survey-manager.js +44 −0 Changes for editor/html/js/survey-manager.js: 44 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -297,6 +297,7 @@ var SurveyManager = (function() { el('sm-survey-title').value = _current.title || ''; renderQuestionTree(); renderQuestionEditor(null); loadConnectionsForPublish(); } function renderQuestionTree() { Loading Loading @@ -552,6 +553,48 @@ var SurveyManager = (function() { autoSaveXml(); } function loadConnectionsForPublish() { var sel = el('sm-publish-conn'); if (!sel) return; sel.innerHTML = '<option value="">-- Verbindung --</option>'; EditorApi.listConnections().then(function(resp) { (resp.connections || []).forEach(function(c) { var o = document.createElement('option'); o.value = c.id; o.textContent = (c.group ? c.group + ' / ' : '') + c.name; sel.appendChild(o); }); }).catch(function() {}); } function publishSurveyToConnection() { if (!_current || !_current.id) { alert('Bitte zuerst die Umfrage speichern.'); return; } var connId = el('sm-publish-conn').value; if (!connId) { alert('Bitte eine Verbindung auswählen.'); return; } var statusEl = el('sm-publish-status'); statusEl.textContent = 'Verbinde…'; statusEl.className = 'set-status'; // Connect first, then publish EditorApi.connectToConnection(connId).then(function() { statusEl.textContent = 'Umfrage wird übertragen…'; return EditorApi.publishSurvey(_current.id, connId); }).then(function(resp) { statusEl.textContent = resp.message || 'Erfolgreich übertragen'; statusEl.className = 'set-status success'; setTimeout(function() { statusEl.textContent = ''; }, 3000); }).catch(function(err) { statusEl.textContent = 'Fehler: ' + (err.error || err.message || JSON.stringify(err)); statusEl.className = 'set-status error'; }); } function autoSaveXml() { if (!_current || !_current.id) return; var xml = questionsToXml( Loading Loading @@ -589,6 +632,7 @@ var SurveyManager = (function() { el('btn-sm-save-survey').addEventListener('click', saveSurveyMeta); el('btn-sm-delete-survey').addEventListener('click', deleteSurvey); el('btn-sm-add-question').addEventListener('click', addQuestion); el('btn-sm-publish').addEventListener('click', publishSurveyToConnection); el('btn-sm-close').addEventListener('click', function() { document.getElementById('survey-manager-dialog').close(); }); Loading editor/src/webedit_api.cpp +267 −0 Changes for editor/src/webedit_api.cpp: 267 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -774,6 +774,12 @@ bool webedit::Api::handleRequest(libhttppp::HttpRequest &curreq, const int tid, return true; } // Route: /api/survey/publish (POST) — push survey to connected blogi if (path == "/api/survey/publish") { handlePublishSurveyToConnection(curreq, sessionid); return true; } // Route: /api/survey/delete/{id} (DELETE) if (path.find("/api/survey/delete/") == 0) { std::string surveyId = path.substr(19); Loading Loading @@ -3226,6 +3232,15 @@ void webedit::Api::handlePublishToConnection(libhttppp::HttpRequest &curreq, return; } // Auto-publish surveys embedded in this document to the target blogi first, // so they exist server-side before the rendered HTML references them. { std::string uid; _session.getData(sessionid, "uid", uid); std::string docXml = exportTreeXml(doc); autoPublishSurveysForDocument(docXml, authid, blogUrl, uid); } std::string html = renderTree(doc.root); // Build the publish command: Loading Loading @@ -3853,6 +3868,151 @@ webedit::Api::NodeContext webedit::Api::findNodeContextInChildren( return {}; } // --------------------------------------------------------------------------- // Survey XML → blogi JSON conversion // // Editor XML format: // <Survey name="..." title="..."> // <Question id="..." parent_id="..." type="..." label="..." order_idx="0"> // <Option value="...">label text</Option> // </Question> // </Survey> // // Blogi JSON format expected by save_survey: // { "id":"", "title":"...", "description":"", // "questions":[{"id":"...","type":"...","text":"...","required":false, // "order":0, "options":[{"value":"...","label":"..."}]}] } // --------------------------------------------------------------------------- json_object* webedit::Api::surveyXmlToJson(const std::string &name, const std::string &title, const std::string &xml) { json_object *jtpl = json_object_new_object(); json_object_object_add(jtpl, "id", json_object_new_string(name.c_str())); json_object_object_add(jtpl, "title", json_object_new_string(title.c_str())); json_object_object_add(jtpl, "description", json_object_new_string("")); json_object *jquestions = json_object_new_array(); if (!xml.empty()) { tinyxml2::XMLDocument doc; if (doc.Parse(xml.c_str(), xml.size()) == tinyxml2::XML_SUCCESS) { tinyxml2::XMLElement *surveyEl = doc.FirstChildElement("Survey"); if (surveyEl) { int orderIdx = 0; for (tinyxml2::XMLElement *qEl = surveyEl->FirstChildElement("Question"); qEl; qEl = qEl->NextSiblingElement("Question")) { json_object *jq = json_object_new_object(); const char *qid = qEl->Attribute("id"); const char *qtype = qEl->Attribute("type"); const char *qlabel = qEl->Attribute("label"); // order_idx from attribute; fall back to sequential int qorder = orderIdx; qEl->QueryIntAttribute("order_idx", &qorder); json_object_object_add(jq, "id", json_object_new_string(qid ? qid : "")); json_object_object_add(jq, "type", json_object_new_string(qtype ? qtype : "text")); json_object_object_add(jq, "text", json_object_new_string(qlabel ? qlabel : "")); json_object_object_add(jq, "required", json_object_new_boolean(false)); json_object_object_add(jq, "order", json_object_new_int(qorder)); json_object *jopts = json_object_new_array(); for (tinyxml2::XMLElement *optEl = qEl->FirstChildElement("Option"); optEl; optEl = optEl->NextSiblingElement("Option")) { json_object *jo = json_object_new_object(); const char *val = optEl->Attribute("value"); const char *lbl = optEl->GetText(); json_object_object_add(jo, "value", json_object_new_string(val ? val : "")); json_object_object_add(jo, "label", json_object_new_string(lbl ? lbl : "")); json_object_array_add(jopts, jo); } json_object_object_add(jq, "options", jopts); json_object_array_add(jquestions, jq); ++orderIdx; } } } } json_object_object_add(jtpl, "questions", jquestions); return jtpl; } void webedit::Api::autoPublishSurveysForDocument(const std::string &docXml, const std::string &authid, const std::string &blogUrl, const std::string &ownerUid) { if (docXml.empty() || authid.empty() || blogUrl.empty()) return; // Collect unique survey_name values from <Survey> elements in doc XML tinyxml2::XMLDocument doc; if (doc.Parse(docXml.c_str(), docXml.size()) != tinyxml2::XML_SUCCESS) return; std::vector<std::string> surveyNames; std::function<void(tinyxml2::XMLElement*)> walk = [&](tinyxml2::XMLElement *el) { if (!el) return; // The survey widget serialises as <Survey survey_name="..." .../> const char *sn = el->Attribute("survey_name"); if (sn && sn[0]) { std::string name(sn); bool found = false; for (auto &s : surveyNames) { if (s == name) { found = true; break; } } if (!found) surveyNames.push_back(name); } for (auto *child = el->FirstChildElement(); child; child = child->NextSiblingElement()) walk(child); }; walk(doc.RootElement()); if (surveyNames.empty()) return; // For each survey name, fetch from editor DB and push to blogi for (const auto &sname : surveyNames) { // Find survey by name in owner's surveys dbpp::SQL sql; dbpp::DBResult res; std::vector<char> eOwner, eName; sql << "SELECT id, name, title, xml FROM survey_templates WHERE owner_uid='" << dbpp::SQL::escaped(eOwner, ownerUid.c_str()) << "' AND name='" << dbpp::SQL::escaped(eName, sname.c_str()) << "' LIMIT 1;"; int rows = _db.getDb().exec(sql, res); if (rows < 1) continue; std::string sid = res[0][0]; std::string sname2 = res[0][1]; std::string stitle = res[0][2]; std::string sxml = res[0][3] ? res[0][3] : ""; // Convert editor XML → blogi JSON json_object *jsurvey = surveyXmlToJson(sname2, stitle, sxml); // Build blogi API request: [{authid:...}, {command:"save_survey", name:..., survey:{...}}] json_object *requestArr = json_object_new_array(); json_object *authCmd = json_object_new_object(); json_object_object_add(authCmd, "authid", json_object_new_string(authid.c_str())); json_object_array_add(requestArr, authCmd); json_object *saveCmd = json_object_new_object(); json_object_object_add(saveCmd, "command", json_object_new_string("save_survey")); json_object_object_add(saveCmd, "name", json_object_new_string(sname2.c_str())); json_object_object_add(saveCmd, "survey", jsurvey); json_object_array_add(requestArr, saveCmd); try { json_object *resp = blogApiCall(blogUrl, requestArr); if (resp) json_object_put(resp); } catch (...) { std::cerr << "autoPublishSurveys: failed to push survey '" << sname2 << "'" << std::endl; } json_object_put(requestArr); } } // --------------------------------------------------------------------------- // Survey management handlers // --------------------------------------------------------------------------- Loading Loading @@ -3951,3 +4111,110 @@ void webedit::Api::handleDeleteSurvey(libhttppp::HttpRequest &curreq, sendJson(curreq, resp); json_object_put(resp); } void webedit::Api::handlePublishSurveyToConnection(libhttppp::HttpRequest &curreq, const std::string &sessionid) { std::string uid; _session.getData(sessionid, "uid", uid); std::string body = getRequestBody(curreq); json_object *req = json_tokener_parse(body.c_str()); if (!req) { sendJsonError(curreq, 400, "Invalid JSON"); return; } json_object *surveyIdObj = nullptr, *connIdObj = nullptr; json_object_object_get_ex(req, "survey_id", &surveyIdObj); json_object_object_get_ex(req, "conn_id", &connIdObj); std::string surveyId = surveyIdObj ? json_object_get_string(surveyIdObj) : ""; std::string connId = connIdObj ? json_object_get_string(connIdObj) : ""; json_object_put(req); if (surveyId.empty() || connId.empty()) { sendJsonError(curreq, 400, "survey_id and conn_id required"); return; } // Fetch survey from editor DB std::string surveyJson = _db.getSurvey(uid, surveyId); json_object *surveyData = json_tokener_parse(surveyJson.c_str()); if (!surveyData) { sendJsonError(curreq, 404, "Survey not found"); return; } json_object *nameObj = nullptr, *titleObj = nullptr, *xmlObj = nullptr; json_object_object_get_ex(surveyData, "name", &nameObj); json_object_object_get_ex(surveyData, "title", &titleObj); json_object_object_get_ex(surveyData, "xml", &xmlObj); std::string sname = nameObj ? json_object_get_string(nameObj) : ""; std::string stitle = titleObj ? json_object_get_string(titleObj) : ""; std::string sxml = xmlObj ? json_object_get_string(xmlObj) : ""; json_object_put(surveyData); if (sname.empty()) { sendJsonError(curreq, 400, "Survey has no name"); return; } // Get active session for the connection std::string authid, blogUrl; { std::lock_guard<std::mutex> lk(_connSessionMtx); auto it = _connSessions.find(connId); if (it == _connSessions.end()) { sendJsonError(curreq, 403, "Not connected. Bitte zuerst verbinden."); return; } authid = it->second.authid; blogUrl = it->second.blogUrl; } // Convert editor XML → blogi JSON json_object *jsurvey = surveyXmlToJson(sname, stitle, sxml); json_object *requestArr = json_object_new_array(); json_object *authCmd = json_object_new_object(); json_object_object_add(authCmd, "authid", json_object_new_string(authid.c_str())); json_object_array_add(requestArr, authCmd); json_object *saveCmd = json_object_new_object(); json_object_object_add(saveCmd, "command", json_object_new_string("save_survey")); json_object_object_add(saveCmd, "name", json_object_new_string(sname.c_str())); json_object_object_add(saveCmd, "survey", jsurvey); json_object_array_add(requestArr, saveCmd); try { json_object *pubResp = blogApiCall(blogUrl, requestArr); json_object_put(requestArr); std::string status = "error"; std::string message = "No response"; if (pubResp && json_object_is_type(pubResp, json_type_array)) { size_t rLen = json_object_array_length(pubResp); for (size_t i = 0; i < rLen; ++i) { json_object *item = json_object_array_get_idx(pubResp, i); json_object *sObj = nullptr, *mObj = nullptr; if (json_object_object_get_ex(item, "status", &sObj)) status = json_object_get_string(sObj); if (json_object_object_get_ex(item, "message", &mObj)) message = json_object_get_string(mObj); } } if (pubResp) json_object_put(pubResp); json_object *resp = json_object_new_object(); json_object_object_add(resp, "status", json_object_new_string(status.c_str())); json_object_object_add(resp, "message", json_object_new_string(message.c_str())); sendJson(curreq, resp); json_object_put(resp); } catch (const std::exception &e) { json_object_put(requestArr); sendJsonError(curreq, 502, std::string("Publish failed: ") + e.what()); } } editor/src/webedit_api.h +9 −0 Changes for editor/src/webedit_api.h: 9 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -149,6 +149,15 @@ namespace webedit { void handleSaveSurvey(libhttppp::HttpRequest &curreq, const std::string &sessionid); void handleDeleteSurvey(libhttppp::HttpRequest &curreq, const std::string &sessionid, const std::string &surveyId); void handlePublishSurveyToConnection(libhttppp::HttpRequest &curreq, const std::string &sessionid); // Survey XML → blogi JSON conversion + auto-publish helpers json_object* surveyXmlToJson(const std::string &name, const std::string &title, const std::string &xml); void autoPublishSurveysForDocument(const std::string &docXml, const std::string &authid, const std::string &blogUrl, const std::string &ownerUid); // HTML import/export handlers void handleImportHtml(libhttppp::HttpRequest &curreq, const std::string &sessionid); Loading Loading
editor/html/index.html +11 −1 Changes for editor/html/index.html: 11 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -398,7 +398,7 @@ <div class="set-content sm-content"> <div id="sm-form-area" style="display:none"> <input type="hidden" id="sm-survey-id"> <div style="display:flex;gap:0.5em;align-items:flex-end;flex-wrap:wrap;margin-bottom:0.8em"> <div style="display:flex;gap:0.5em;align-items:flex-end;flex-wrap:wrap;margin-bottom:0.6em"> <div class="set-field" style="flex:1;min-width:140px;margin:0"> <label>Name (intern)</label> <input type="text" id="sm-survey-name" placeholder="kundenzufriedenheit"> Loading @@ -414,6 +414,16 @@ </div> </div> <!-- Publish to blogi --> <div style="display:flex;gap:0.4em;align-items:center;flex-wrap:wrap;margin-bottom:0.8em;padding:0.5em;background:var(--bg-tertiary);border-radius:6px;border:1px solid var(--border)"> <span style="font-size:0.8rem;color:var(--text-secondary);white-space:nowrap">🌐 Zu Blogi übertragen:</span> <select id="sm-publish-conn" style="flex:1;min-width:140px;font-size:0.8rem;padding:0.2em 0.4em;border-radius:4px;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text-primary)"> <option value="">-- Verbindung wählen --</option> </select> <button id="btn-sm-publish" class="set-save-btn" style="background:var(--accent);white-space:nowrap">↗ Publish</button> <span id="sm-publish-status" class="set-status"></span> </div> <div class="sm-editor-layout"> <!-- Canvas tree --> <div class="sm-canvas-wrap"> Loading
editor/html/js/api.js +4 −0 Changes for editor/html/js/api.js: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -416,6 +416,10 @@ var EditorApi = (function() { deleteSurvey: function(id) { return request('DELETE', '/api/survey/delete/' + encodeURIComponent(id)); }, publishSurvey: function(surveyId, connId) { return request('POST', '/api/survey/publish', { survey_id: surveyId, conn_id: connId }); } }; })();
editor/html/js/survey-manager.js +44 −0 Changes for editor/html/js/survey-manager.js: 44 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -297,6 +297,7 @@ var SurveyManager = (function() { el('sm-survey-title').value = _current.title || ''; renderQuestionTree(); renderQuestionEditor(null); loadConnectionsForPublish(); } function renderQuestionTree() { Loading Loading @@ -552,6 +553,48 @@ var SurveyManager = (function() { autoSaveXml(); } function loadConnectionsForPublish() { var sel = el('sm-publish-conn'); if (!sel) return; sel.innerHTML = '<option value="">-- Verbindung --</option>'; EditorApi.listConnections().then(function(resp) { (resp.connections || []).forEach(function(c) { var o = document.createElement('option'); o.value = c.id; o.textContent = (c.group ? c.group + ' / ' : '') + c.name; sel.appendChild(o); }); }).catch(function() {}); } function publishSurveyToConnection() { if (!_current || !_current.id) { alert('Bitte zuerst die Umfrage speichern.'); return; } var connId = el('sm-publish-conn').value; if (!connId) { alert('Bitte eine Verbindung auswählen.'); return; } var statusEl = el('sm-publish-status'); statusEl.textContent = 'Verbinde…'; statusEl.className = 'set-status'; // Connect first, then publish EditorApi.connectToConnection(connId).then(function() { statusEl.textContent = 'Umfrage wird übertragen…'; return EditorApi.publishSurvey(_current.id, connId); }).then(function(resp) { statusEl.textContent = resp.message || 'Erfolgreich übertragen'; statusEl.className = 'set-status success'; setTimeout(function() { statusEl.textContent = ''; }, 3000); }).catch(function(err) { statusEl.textContent = 'Fehler: ' + (err.error || err.message || JSON.stringify(err)); statusEl.className = 'set-status error'; }); } function autoSaveXml() { if (!_current || !_current.id) return; var xml = questionsToXml( Loading Loading @@ -589,6 +632,7 @@ var SurveyManager = (function() { el('btn-sm-save-survey').addEventListener('click', saveSurveyMeta); el('btn-sm-delete-survey').addEventListener('click', deleteSurvey); el('btn-sm-add-question').addEventListener('click', addQuestion); el('btn-sm-publish').addEventListener('click', publishSurveyToConnection); el('btn-sm-close').addEventListener('click', function() { document.getElementById('survey-manager-dialog').close(); }); Loading
editor/src/webedit_api.cpp +267 −0 Changes for editor/src/webedit_api.cpp: 267 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -774,6 +774,12 @@ bool webedit::Api::handleRequest(libhttppp::HttpRequest &curreq, const int tid, return true; } // Route: /api/survey/publish (POST) — push survey to connected blogi if (path == "/api/survey/publish") { handlePublishSurveyToConnection(curreq, sessionid); return true; } // Route: /api/survey/delete/{id} (DELETE) if (path.find("/api/survey/delete/") == 0) { std::string surveyId = path.substr(19); Loading Loading @@ -3226,6 +3232,15 @@ void webedit::Api::handlePublishToConnection(libhttppp::HttpRequest &curreq, return; } // Auto-publish surveys embedded in this document to the target blogi first, // so they exist server-side before the rendered HTML references them. { std::string uid; _session.getData(sessionid, "uid", uid); std::string docXml = exportTreeXml(doc); autoPublishSurveysForDocument(docXml, authid, blogUrl, uid); } std::string html = renderTree(doc.root); // Build the publish command: Loading Loading @@ -3853,6 +3868,151 @@ webedit::Api::NodeContext webedit::Api::findNodeContextInChildren( return {}; } // --------------------------------------------------------------------------- // Survey XML → blogi JSON conversion // // Editor XML format: // <Survey name="..." title="..."> // <Question id="..." parent_id="..." type="..." label="..." order_idx="0"> // <Option value="...">label text</Option> // </Question> // </Survey> // // Blogi JSON format expected by save_survey: // { "id":"", "title":"...", "description":"", // "questions":[{"id":"...","type":"...","text":"...","required":false, // "order":0, "options":[{"value":"...","label":"..."}]}] } // --------------------------------------------------------------------------- json_object* webedit::Api::surveyXmlToJson(const std::string &name, const std::string &title, const std::string &xml) { json_object *jtpl = json_object_new_object(); json_object_object_add(jtpl, "id", json_object_new_string(name.c_str())); json_object_object_add(jtpl, "title", json_object_new_string(title.c_str())); json_object_object_add(jtpl, "description", json_object_new_string("")); json_object *jquestions = json_object_new_array(); if (!xml.empty()) { tinyxml2::XMLDocument doc; if (doc.Parse(xml.c_str(), xml.size()) == tinyxml2::XML_SUCCESS) { tinyxml2::XMLElement *surveyEl = doc.FirstChildElement("Survey"); if (surveyEl) { int orderIdx = 0; for (tinyxml2::XMLElement *qEl = surveyEl->FirstChildElement("Question"); qEl; qEl = qEl->NextSiblingElement("Question")) { json_object *jq = json_object_new_object(); const char *qid = qEl->Attribute("id"); const char *qtype = qEl->Attribute("type"); const char *qlabel = qEl->Attribute("label"); // order_idx from attribute; fall back to sequential int qorder = orderIdx; qEl->QueryIntAttribute("order_idx", &qorder); json_object_object_add(jq, "id", json_object_new_string(qid ? qid : "")); json_object_object_add(jq, "type", json_object_new_string(qtype ? qtype : "text")); json_object_object_add(jq, "text", json_object_new_string(qlabel ? qlabel : "")); json_object_object_add(jq, "required", json_object_new_boolean(false)); json_object_object_add(jq, "order", json_object_new_int(qorder)); json_object *jopts = json_object_new_array(); for (tinyxml2::XMLElement *optEl = qEl->FirstChildElement("Option"); optEl; optEl = optEl->NextSiblingElement("Option")) { json_object *jo = json_object_new_object(); const char *val = optEl->Attribute("value"); const char *lbl = optEl->GetText(); json_object_object_add(jo, "value", json_object_new_string(val ? val : "")); json_object_object_add(jo, "label", json_object_new_string(lbl ? lbl : "")); json_object_array_add(jopts, jo); } json_object_object_add(jq, "options", jopts); json_object_array_add(jquestions, jq); ++orderIdx; } } } } json_object_object_add(jtpl, "questions", jquestions); return jtpl; } void webedit::Api::autoPublishSurveysForDocument(const std::string &docXml, const std::string &authid, const std::string &blogUrl, const std::string &ownerUid) { if (docXml.empty() || authid.empty() || blogUrl.empty()) return; // Collect unique survey_name values from <Survey> elements in doc XML tinyxml2::XMLDocument doc; if (doc.Parse(docXml.c_str(), docXml.size()) != tinyxml2::XML_SUCCESS) return; std::vector<std::string> surveyNames; std::function<void(tinyxml2::XMLElement*)> walk = [&](tinyxml2::XMLElement *el) { if (!el) return; // The survey widget serialises as <Survey survey_name="..." .../> const char *sn = el->Attribute("survey_name"); if (sn && sn[0]) { std::string name(sn); bool found = false; for (auto &s : surveyNames) { if (s == name) { found = true; break; } } if (!found) surveyNames.push_back(name); } for (auto *child = el->FirstChildElement(); child; child = child->NextSiblingElement()) walk(child); }; walk(doc.RootElement()); if (surveyNames.empty()) return; // For each survey name, fetch from editor DB and push to blogi for (const auto &sname : surveyNames) { // Find survey by name in owner's surveys dbpp::SQL sql; dbpp::DBResult res; std::vector<char> eOwner, eName; sql << "SELECT id, name, title, xml FROM survey_templates WHERE owner_uid='" << dbpp::SQL::escaped(eOwner, ownerUid.c_str()) << "' AND name='" << dbpp::SQL::escaped(eName, sname.c_str()) << "' LIMIT 1;"; int rows = _db.getDb().exec(sql, res); if (rows < 1) continue; std::string sid = res[0][0]; std::string sname2 = res[0][1]; std::string stitle = res[0][2]; std::string sxml = res[0][3] ? res[0][3] : ""; // Convert editor XML → blogi JSON json_object *jsurvey = surveyXmlToJson(sname2, stitle, sxml); // Build blogi API request: [{authid:...}, {command:"save_survey", name:..., survey:{...}}] json_object *requestArr = json_object_new_array(); json_object *authCmd = json_object_new_object(); json_object_object_add(authCmd, "authid", json_object_new_string(authid.c_str())); json_object_array_add(requestArr, authCmd); json_object *saveCmd = json_object_new_object(); json_object_object_add(saveCmd, "command", json_object_new_string("save_survey")); json_object_object_add(saveCmd, "name", json_object_new_string(sname2.c_str())); json_object_object_add(saveCmd, "survey", jsurvey); json_object_array_add(requestArr, saveCmd); try { json_object *resp = blogApiCall(blogUrl, requestArr); if (resp) json_object_put(resp); } catch (...) { std::cerr << "autoPublishSurveys: failed to push survey '" << sname2 << "'" << std::endl; } json_object_put(requestArr); } } // --------------------------------------------------------------------------- // Survey management handlers // --------------------------------------------------------------------------- Loading Loading @@ -3951,3 +4111,110 @@ void webedit::Api::handleDeleteSurvey(libhttppp::HttpRequest &curreq, sendJson(curreq, resp); json_object_put(resp); } void webedit::Api::handlePublishSurveyToConnection(libhttppp::HttpRequest &curreq, const std::string &sessionid) { std::string uid; _session.getData(sessionid, "uid", uid); std::string body = getRequestBody(curreq); json_object *req = json_tokener_parse(body.c_str()); if (!req) { sendJsonError(curreq, 400, "Invalid JSON"); return; } json_object *surveyIdObj = nullptr, *connIdObj = nullptr; json_object_object_get_ex(req, "survey_id", &surveyIdObj); json_object_object_get_ex(req, "conn_id", &connIdObj); std::string surveyId = surveyIdObj ? json_object_get_string(surveyIdObj) : ""; std::string connId = connIdObj ? json_object_get_string(connIdObj) : ""; json_object_put(req); if (surveyId.empty() || connId.empty()) { sendJsonError(curreq, 400, "survey_id and conn_id required"); return; } // Fetch survey from editor DB std::string surveyJson = _db.getSurvey(uid, surveyId); json_object *surveyData = json_tokener_parse(surveyJson.c_str()); if (!surveyData) { sendJsonError(curreq, 404, "Survey not found"); return; } json_object *nameObj = nullptr, *titleObj = nullptr, *xmlObj = nullptr; json_object_object_get_ex(surveyData, "name", &nameObj); json_object_object_get_ex(surveyData, "title", &titleObj); json_object_object_get_ex(surveyData, "xml", &xmlObj); std::string sname = nameObj ? json_object_get_string(nameObj) : ""; std::string stitle = titleObj ? json_object_get_string(titleObj) : ""; std::string sxml = xmlObj ? json_object_get_string(xmlObj) : ""; json_object_put(surveyData); if (sname.empty()) { sendJsonError(curreq, 400, "Survey has no name"); return; } // Get active session for the connection std::string authid, blogUrl; { std::lock_guard<std::mutex> lk(_connSessionMtx); auto it = _connSessions.find(connId); if (it == _connSessions.end()) { sendJsonError(curreq, 403, "Not connected. Bitte zuerst verbinden."); return; } authid = it->second.authid; blogUrl = it->second.blogUrl; } // Convert editor XML → blogi JSON json_object *jsurvey = surveyXmlToJson(sname, stitle, sxml); json_object *requestArr = json_object_new_array(); json_object *authCmd = json_object_new_object(); json_object_object_add(authCmd, "authid", json_object_new_string(authid.c_str())); json_object_array_add(requestArr, authCmd); json_object *saveCmd = json_object_new_object(); json_object_object_add(saveCmd, "command", json_object_new_string("save_survey")); json_object_object_add(saveCmd, "name", json_object_new_string(sname.c_str())); json_object_object_add(saveCmd, "survey", jsurvey); json_object_array_add(requestArr, saveCmd); try { json_object *pubResp = blogApiCall(blogUrl, requestArr); json_object_put(requestArr); std::string status = "error"; std::string message = "No response"; if (pubResp && json_object_is_type(pubResp, json_type_array)) { size_t rLen = json_object_array_length(pubResp); for (size_t i = 0; i < rLen; ++i) { json_object *item = json_object_array_get_idx(pubResp, i); json_object *sObj = nullptr, *mObj = nullptr; if (json_object_object_get_ex(item, "status", &sObj)) status = json_object_get_string(sObj); if (json_object_object_get_ex(item, "message", &mObj)) message = json_object_get_string(mObj); } } if (pubResp) json_object_put(pubResp); json_object *resp = json_object_new_object(); json_object_object_add(resp, "status", json_object_new_string(status.c_str())); json_object_object_add(resp, "message", json_object_new_string(message.c_str())); sendJson(curreq, resp); json_object_put(resp); } catch (const std::exception &e) { json_object_put(requestArr); sendJsonError(curreq, 502, std::string("Publish failed: ") + e.what()); } }
editor/src/webedit_api.h +9 −0 Changes for editor/src/webedit_api.h: 9 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -149,6 +149,15 @@ namespace webedit { void handleSaveSurvey(libhttppp::HttpRequest &curreq, const std::string &sessionid); void handleDeleteSurvey(libhttppp::HttpRequest &curreq, const std::string &sessionid, const std::string &surveyId); void handlePublishSurveyToConnection(libhttppp::HttpRequest &curreq, const std::string &sessionid); // Survey XML → blogi JSON conversion + auto-publish helpers json_object* surveyXmlToJson(const std::string &name, const std::string &title, const std::string &xml); void autoPublishSurveysForDocument(const std::string &docXml, const std::string &authid, const std::string &blogUrl, const std::string &ownerUid); // HTML import/export handlers void handleImportHtml(libhttppp::HttpRequest &curreq, const std::string &sessionid); Loading