Commit 99e0a772 authored by jan.koester's avatar jan.koester
Browse files

test

parent f336e48f
Loading
Loading
Loading
Loading
+11 −1
Changes for editor/html/index.html: 11 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -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">
@@ -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">&#x1F310; 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">&#x2197; Publish</button>
                        <span id="sm-publish-status" class="set-status"></span>
                    </div>

                    <div class="sm-editor-layout">
                        <!-- Canvas tree -->
                        <div class="sm-canvas-wrap">
+4 −0
Changes for editor/html/js/api.js: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -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 });
        }
    };
})();
+44 −0
Changes for editor/html/js/survey-manager.js: 44 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -297,6 +297,7 @@ var SurveyManager = (function() {
        el('sm-survey-title').value = _current.title || '';
        renderQuestionTree();
        renderQuestionEditor(null);
        loadConnectionsForPublish();
    }

    function renderQuestionTree() {
@@ -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(
@@ -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();
        });
+267 −0
Changes for editor/src/webedit_api.cpp: 267 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -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);
@@ -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:
@@ -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
// ---------------------------------------------------------------------------
@@ -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());
    }
}
+9 −0
Changes for editor/src/webedit_api.h: 9 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -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);