Loading editor/html/js/api.js +8 −2 Original line number Diff line number Diff line Loading @@ -173,10 +173,16 @@ var EditorApi = (function() { return request('POST', '/api/document/save', { name: name, group: group || '' }); }, bulkGenerateTemplates: function(templateIds, rows) { listTemplates: function() { return request('GET', '/api/templates'); }, bulkGenerateTemplates: function(templateIds, rows, overwriteExisting, checkOnly) { return request('POST', '/api/template/bulk-generate', { template_ids: templateIds || [], rows: rows || [] rows: rows || [], overwrite_existing: !!overwriteExisting, check_only: !!checkOnly }); }, Loading editor/html/js/editor.js +36 −10 Original line number Diff line number Diff line Loading @@ -460,8 +460,8 @@ wizardCsvRows = []; wizardCsvColumns = []; EditorApi.listDocuments().then(function(resp) { wizardTemplates = resp.documents || []; EditorApi.listTemplates().then(function(resp) { wizardTemplates = resp.templates || []; renderTemplateWizardTemplateList(); document.getElementById('template-wizard-dialog').showModal(); }).catch(function(err) { Loading Loading @@ -516,20 +516,46 @@ return; } status.textContent = 'Erzeuge Dokumente...'; status.textContent = 'Prufe bestehende Vorlagen...'; status.className = 'set-status'; EditorApi.bulkGenerateTemplates(selectedIds, wizardCsvRows, false, true).then(function(previewResp) { var conflicts = previewResp.conflicts_count || 0; if (conflicts > 0) { var q = conflicts + ' Vorlage(n) mit gleichem Name in derselben Gruppe existieren bereits. Uberschreiben?'; if (!confirm(q)) { status.textContent = 'Abgebrochen: vorhandene Vorlagen wurden nicht uberschrieben.'; status.className = 'set-status'; return; } executeTemplateBulk(selectedIds, true); return; } executeTemplateBulk(selectedIds, false); }).catch(function(err) { status.textContent = 'Fehler bei Konfliktprufung: ' + ((err && err.error) || 'Unbekannt'); status.className = 'set-status error'; }); }); } function executeTemplateBulk(selectedIds, overwriteExisting) { var status = document.getElementById('template-wizard-status'); status.textContent = overwriteExisting ? 'Uberschreibe bestehende Vorlagen...' : 'Erzeuge Dokumente...'; status.className = 'set-status'; EditorApi.bulkGenerateTemplates(selectedIds, wizardCsvRows).then(function(resp) { EditorApi.bulkGenerateTemplates(selectedIds, wizardCsvRows, overwriteExisting, false).then(function(resp) { var created = resp.created_count || 0; var rows = resp.row_count || wizardCsvRows.length; var templates = resp.template_count || selectedIds.length; status.textContent = 'Fertig: ' + created + ' Dokumente aus ' + templates + ' Vorlagen und ' + rows + ' CSV-Zeilen erzeugt.'; status.textContent = 'Fertig: ' + created + ' Dokumente aus ' + templates + ' Vorlagen und ' + rows + ' CSV-Zeilen verarbeitet.'; status.className = 'set-status success'; }).catch(function(err) { status.textContent = 'Fehler: ' + ((err && err.error) || 'Erzeugen fehlgeschlagen'); status.className = 'set-status error'; }); }); } function renderTemplateWizardTemplateList() { Loading editor/src/webedit_api.cpp +88 −8 Original line number Diff line number Diff line Loading @@ -489,6 +489,12 @@ bool webedit::Api::handleRequest(libhttppp::HttpRequest &curreq, const int tid, return true; } // Route: /api/templates if (path == "/api/templates") { handleListTemplates(curreq, sessionid); return true; } // Route: /api/template/bulk-generate if (path == "/api/template/bulk-generate") { handleTemplateBulkGenerate(curreq, sessionid); Loading Loading @@ -1532,8 +1538,15 @@ void webedit::Api::handleTemplateBulkGenerate(libhttppp::HttpRequest &curreq, json_object *templateIdsObj = nullptr; json_object *rowsObj = nullptr; json_object *overwriteExistingObj = nullptr; json_object *checkOnlyObj = nullptr; json_object_object_get_ex(req, "template_ids", &templateIdsObj); json_object_object_get_ex(req, "rows", &rowsObj); json_object_object_get_ex(req, "overwrite_existing", &overwriteExistingObj); json_object_object_get_ex(req, "check_only", &checkOnlyObj); const bool overwriteExisting = overwriteExistingObj && json_object_get_boolean(overwriteExistingObj); const bool checkOnly = checkOnlyObj && json_object_get_boolean(checkOnlyObj); if (!templateIdsObj || !rowsObj || !json_object_is_type(templateIdsObj, json_type_array) || Loading Loading @@ -1596,7 +1609,9 @@ void webedit::Api::handleTemplateBulkGenerate(libhttppp::HttpRequest &curreq, } json_object *createdArr = json_object_new_array(); json_object *conflictsArr = json_object_new_array(); size_t createdCount = 0; size_t conflictsCount = 0; for (size_t rowIndex = 0; rowIndex < rowCount; ++rowIndex) { json_object *rowObj = json_object_array_get_idx(rowsObj, rowIndex); Loading Loading @@ -1631,20 +1646,57 @@ void webedit::Api::handleTemplateBulkGenerate(libhttppp::HttpRequest &curreq, const std::string generatedXml = replacePlaceholders(tpl.xml, rowValues); const std::string targetGroup = rowDomain.empty() ? tpl.group : rowDomain; std::string newDocId = _db.saveDocument(uid, std::string existingDocId; const bool exists = _db.findDocumentIdByNameGroup(uid, sessionDomain, tpl.name, targetGroup, existingDocId); if (exists && !overwriteExisting) { ++conflictsCount; json_object *conflictObj = json_object_new_object(); json_object_object_add(conflictObj, "id", json_object_new_string(existingDocId.c_str())); json_object_object_add(conflictObj, "name", json_object_new_string(tpl.name.c_str())); json_object_object_add(conflictObj, "group", json_object_new_string(targetGroup.c_str())); json_object_object_add(conflictObj, "template_id", json_object_new_string(tpl.id.c_str())); json_object_object_add(conflictObj, "row_index", json_object_new_int64(static_cast<long long>(rowIndex))); json_object_array_add(conflictsArr, conflictObj); continue; } if (checkOnly) { continue; } std::string changedDocId; bool updated = false; if (exists && overwriteExisting) { changedDocId = _db.updateDocument(uid, existingDocId, tpl.name, generatedXml, author, targetGroup); if (!newDocId.empty()) { updated = true; } else { changedDocId = _db.saveDocument(uid, sessionDomain, tpl.name, generatedXml, author, targetGroup); } if (!changedDocId.empty()) { ++createdCount; json_object *createdObj = json_object_new_object(); json_object_object_add(createdObj, "id", json_object_new_string(newDocId.c_str())); json_object_object_add(createdObj, "id", json_object_new_string(changedDocId.c_str())); json_object_object_add(createdObj, "name", json_object_new_string(tpl.name.c_str())); json_object_object_add(createdObj, "group", json_object_new_string(targetGroup.c_str())); json_object_object_add(createdObj, "template_id", json_object_new_string(tpl.id.c_str())); json_object_object_add(createdObj, "row_index", json_object_new_int64(static_cast<long long>(rowIndex))); json_object_object_add(createdObj, "updated", json_object_new_boolean(updated)); json_object_array_add(createdArr, createdObj); } } Loading @@ -1653,15 +1705,43 @@ void webedit::Api::handleTemplateBulkGenerate(libhttppp::HttpRequest &curreq, json_object *resp = json_object_new_object(); json_object_object_add(resp, "status", json_object_new_string("ok")); json_object_object_add(resp, "created_count", json_object_new_int64(static_cast<long long>(createdCount))); json_object_object_add(resp, "conflicts_count", json_object_new_int64(static_cast<long long>(conflictsCount))); json_object_object_add(resp, "template_count", json_object_new_int64(static_cast<long long>(templates.size()))); json_object_object_add(resp, "row_count", json_object_new_int64(static_cast<long long>(rowCount))); json_object_object_add(resp, "check_only", json_object_new_boolean(checkOnly)); json_object_object_add(resp, "overwrite_existing", json_object_new_boolean(overwriteExisting)); json_object_object_add(resp, "created", createdArr); json_object_object_add(resp, "conflicts", conflictsArr); sendJson(curreq, resp); json_object_put(resp); json_object_put(req); } void webedit::Api::handleListTemplates(libhttppp::HttpRequest &curreq, const std::string &sessionid) { std::string uid; std::string domain; _session.getData(sessionid, "uid", uid); _session.getData(sessionid, "domain", domain); if (uid.empty()) { sendJsonError(curreq, 400, "Missing user session"); return; } std::string jsonStr = _db.listDocumentsForOwnerDomain(uid, domain); json_object *arr = json_tokener_parse(jsonStr.c_str()); if (!arr) { arr = json_object_new_array(); } json_object *resp = json_object_new_object(); json_object_object_add(resp, "templates", arr); sendJson(curreq, resp); json_object_put(resp); } // --- Load document from DB --- void webedit::Api::handleLoadDocument(libhttppp::HttpRequest &curreq, Loading editor/src/webedit_api.h +1 −0 Original line number Diff line number Diff line Loading @@ -93,6 +93,7 @@ namespace webedit { void handleListDocuments(libhttppp::HttpRequest &curreq, const std::string &sessionid); void handleDeleteDocument(libhttppp::HttpRequest &curreq, const std::string &sessionid, const std::string &docId); void handleListTemplates(libhttppp::HttpRequest &curreq, const std::string &sessionid); void handleTemplateBulkGenerate(libhttppp::HttpRequest &curreq, const std::string &sessionid); // Revision handlers Loading editor/src/webedit_db.cpp +56 −0 Original line number Diff line number Diff line Loading @@ -409,6 +409,62 @@ std::string webedit::Database::listDocuments(const std::string &domain) { return result; } std::string webedit::Database::listDocumentsForOwnerDomain(const std::string &ownerUid, const std::string &domain) { dbpp::SQL sql; dbpp::DBResult res; std::vector<char> escapedUid, escapedDomain; sql << "SELECT id, name, created, modified, group_name FROM documents " << "WHERE owner_uid='" << dbpp::SQL::escaped(escapedUid, ownerUid.c_str()) << "' AND domain='" << dbpp::SQL::escaped(escapedDomain, domain.c_str()) << "' ORDER BY group_name, name, modified DESC;"; int rows = _db->exec(sql, res); json_object *arr = json_object_new_array(); for (int i = 0; i < rows; ++i) { json_object *obj = json_object_new_object(); json_object_object_add(obj, "id", json_object_new_string(res[i][0])); json_object_object_add(obj, "name", json_object_new_string(res[i][1])); json_object_object_add(obj, "created", json_object_new_string(res[i][2])); json_object_object_add(obj, "modified", json_object_new_string(res[i][3])); json_object_object_add(obj, "group", json_object_new_string(res[i][4])); json_object_array_add(arr, obj); } std::string result = json_object_to_json_string_ext(arr, JSON_C_TO_STRING_NOSLASHESCAPE); json_object_put(arr); return result; } bool webedit::Database::findDocumentIdByNameGroup(const std::string &ownerUid, const std::string &domain, const std::string &name, const std::string &group, std::string &docId) { dbpp::SQL sql; dbpp::DBResult res; std::vector<char> escapedUid, escapedDomain, escapedName, escapedGroup; sql << "SELECT id FROM documents WHERE owner_uid='" << dbpp::SQL::escaped(escapedUid, ownerUid.c_str()) << "' AND domain='" << dbpp::SQL::escaped(escapedDomain, domain.c_str()) << "' AND name='" << dbpp::SQL::escaped(escapedName, name.c_str()) << "' AND group_name='" << dbpp::SQL::escaped(escapedGroup, group.c_str()) << "' ORDER BY modified DESC LIMIT 1;"; int rows = _db->exec(sql, res); if (rows < 1) { docId.clear(); return false; } docId = std::string(res[0][0]); return true; } std::string webedit::Database::listRevisions(const std::string &docId) { dbpp::SQL sql; dbpp::DBResult res; Loading Loading
editor/html/js/api.js +8 −2 Original line number Diff line number Diff line Loading @@ -173,10 +173,16 @@ var EditorApi = (function() { return request('POST', '/api/document/save', { name: name, group: group || '' }); }, bulkGenerateTemplates: function(templateIds, rows) { listTemplates: function() { return request('GET', '/api/templates'); }, bulkGenerateTemplates: function(templateIds, rows, overwriteExisting, checkOnly) { return request('POST', '/api/template/bulk-generate', { template_ids: templateIds || [], rows: rows || [] rows: rows || [], overwrite_existing: !!overwriteExisting, check_only: !!checkOnly }); }, Loading
editor/html/js/editor.js +36 −10 Original line number Diff line number Diff line Loading @@ -460,8 +460,8 @@ wizardCsvRows = []; wizardCsvColumns = []; EditorApi.listDocuments().then(function(resp) { wizardTemplates = resp.documents || []; EditorApi.listTemplates().then(function(resp) { wizardTemplates = resp.templates || []; renderTemplateWizardTemplateList(); document.getElementById('template-wizard-dialog').showModal(); }).catch(function(err) { Loading Loading @@ -516,20 +516,46 @@ return; } status.textContent = 'Erzeuge Dokumente...'; status.textContent = 'Prufe bestehende Vorlagen...'; status.className = 'set-status'; EditorApi.bulkGenerateTemplates(selectedIds, wizardCsvRows, false, true).then(function(previewResp) { var conflicts = previewResp.conflicts_count || 0; if (conflicts > 0) { var q = conflicts + ' Vorlage(n) mit gleichem Name in derselben Gruppe existieren bereits. Uberschreiben?'; if (!confirm(q)) { status.textContent = 'Abgebrochen: vorhandene Vorlagen wurden nicht uberschrieben.'; status.className = 'set-status'; return; } executeTemplateBulk(selectedIds, true); return; } executeTemplateBulk(selectedIds, false); }).catch(function(err) { status.textContent = 'Fehler bei Konfliktprufung: ' + ((err && err.error) || 'Unbekannt'); status.className = 'set-status error'; }); }); } function executeTemplateBulk(selectedIds, overwriteExisting) { var status = document.getElementById('template-wizard-status'); status.textContent = overwriteExisting ? 'Uberschreibe bestehende Vorlagen...' : 'Erzeuge Dokumente...'; status.className = 'set-status'; EditorApi.bulkGenerateTemplates(selectedIds, wizardCsvRows).then(function(resp) { EditorApi.bulkGenerateTemplates(selectedIds, wizardCsvRows, overwriteExisting, false).then(function(resp) { var created = resp.created_count || 0; var rows = resp.row_count || wizardCsvRows.length; var templates = resp.template_count || selectedIds.length; status.textContent = 'Fertig: ' + created + ' Dokumente aus ' + templates + ' Vorlagen und ' + rows + ' CSV-Zeilen erzeugt.'; status.textContent = 'Fertig: ' + created + ' Dokumente aus ' + templates + ' Vorlagen und ' + rows + ' CSV-Zeilen verarbeitet.'; status.className = 'set-status success'; }).catch(function(err) { status.textContent = 'Fehler: ' + ((err && err.error) || 'Erzeugen fehlgeschlagen'); status.className = 'set-status error'; }); }); } function renderTemplateWizardTemplateList() { Loading
editor/src/webedit_api.cpp +88 −8 Original line number Diff line number Diff line Loading @@ -489,6 +489,12 @@ bool webedit::Api::handleRequest(libhttppp::HttpRequest &curreq, const int tid, return true; } // Route: /api/templates if (path == "/api/templates") { handleListTemplates(curreq, sessionid); return true; } // Route: /api/template/bulk-generate if (path == "/api/template/bulk-generate") { handleTemplateBulkGenerate(curreq, sessionid); Loading Loading @@ -1532,8 +1538,15 @@ void webedit::Api::handleTemplateBulkGenerate(libhttppp::HttpRequest &curreq, json_object *templateIdsObj = nullptr; json_object *rowsObj = nullptr; json_object *overwriteExistingObj = nullptr; json_object *checkOnlyObj = nullptr; json_object_object_get_ex(req, "template_ids", &templateIdsObj); json_object_object_get_ex(req, "rows", &rowsObj); json_object_object_get_ex(req, "overwrite_existing", &overwriteExistingObj); json_object_object_get_ex(req, "check_only", &checkOnlyObj); const bool overwriteExisting = overwriteExistingObj && json_object_get_boolean(overwriteExistingObj); const bool checkOnly = checkOnlyObj && json_object_get_boolean(checkOnlyObj); if (!templateIdsObj || !rowsObj || !json_object_is_type(templateIdsObj, json_type_array) || Loading Loading @@ -1596,7 +1609,9 @@ void webedit::Api::handleTemplateBulkGenerate(libhttppp::HttpRequest &curreq, } json_object *createdArr = json_object_new_array(); json_object *conflictsArr = json_object_new_array(); size_t createdCount = 0; size_t conflictsCount = 0; for (size_t rowIndex = 0; rowIndex < rowCount; ++rowIndex) { json_object *rowObj = json_object_array_get_idx(rowsObj, rowIndex); Loading Loading @@ -1631,20 +1646,57 @@ void webedit::Api::handleTemplateBulkGenerate(libhttppp::HttpRequest &curreq, const std::string generatedXml = replacePlaceholders(tpl.xml, rowValues); const std::string targetGroup = rowDomain.empty() ? tpl.group : rowDomain; std::string newDocId = _db.saveDocument(uid, std::string existingDocId; const bool exists = _db.findDocumentIdByNameGroup(uid, sessionDomain, tpl.name, targetGroup, existingDocId); if (exists && !overwriteExisting) { ++conflictsCount; json_object *conflictObj = json_object_new_object(); json_object_object_add(conflictObj, "id", json_object_new_string(existingDocId.c_str())); json_object_object_add(conflictObj, "name", json_object_new_string(tpl.name.c_str())); json_object_object_add(conflictObj, "group", json_object_new_string(targetGroup.c_str())); json_object_object_add(conflictObj, "template_id", json_object_new_string(tpl.id.c_str())); json_object_object_add(conflictObj, "row_index", json_object_new_int64(static_cast<long long>(rowIndex))); json_object_array_add(conflictsArr, conflictObj); continue; } if (checkOnly) { continue; } std::string changedDocId; bool updated = false; if (exists && overwriteExisting) { changedDocId = _db.updateDocument(uid, existingDocId, tpl.name, generatedXml, author, targetGroup); if (!newDocId.empty()) { updated = true; } else { changedDocId = _db.saveDocument(uid, sessionDomain, tpl.name, generatedXml, author, targetGroup); } if (!changedDocId.empty()) { ++createdCount; json_object *createdObj = json_object_new_object(); json_object_object_add(createdObj, "id", json_object_new_string(newDocId.c_str())); json_object_object_add(createdObj, "id", json_object_new_string(changedDocId.c_str())); json_object_object_add(createdObj, "name", json_object_new_string(tpl.name.c_str())); json_object_object_add(createdObj, "group", json_object_new_string(targetGroup.c_str())); json_object_object_add(createdObj, "template_id", json_object_new_string(tpl.id.c_str())); json_object_object_add(createdObj, "row_index", json_object_new_int64(static_cast<long long>(rowIndex))); json_object_object_add(createdObj, "updated", json_object_new_boolean(updated)); json_object_array_add(createdArr, createdObj); } } Loading @@ -1653,15 +1705,43 @@ void webedit::Api::handleTemplateBulkGenerate(libhttppp::HttpRequest &curreq, json_object *resp = json_object_new_object(); json_object_object_add(resp, "status", json_object_new_string("ok")); json_object_object_add(resp, "created_count", json_object_new_int64(static_cast<long long>(createdCount))); json_object_object_add(resp, "conflicts_count", json_object_new_int64(static_cast<long long>(conflictsCount))); json_object_object_add(resp, "template_count", json_object_new_int64(static_cast<long long>(templates.size()))); json_object_object_add(resp, "row_count", json_object_new_int64(static_cast<long long>(rowCount))); json_object_object_add(resp, "check_only", json_object_new_boolean(checkOnly)); json_object_object_add(resp, "overwrite_existing", json_object_new_boolean(overwriteExisting)); json_object_object_add(resp, "created", createdArr); json_object_object_add(resp, "conflicts", conflictsArr); sendJson(curreq, resp); json_object_put(resp); json_object_put(req); } void webedit::Api::handleListTemplates(libhttppp::HttpRequest &curreq, const std::string &sessionid) { std::string uid; std::string domain; _session.getData(sessionid, "uid", uid); _session.getData(sessionid, "domain", domain); if (uid.empty()) { sendJsonError(curreq, 400, "Missing user session"); return; } std::string jsonStr = _db.listDocumentsForOwnerDomain(uid, domain); json_object *arr = json_tokener_parse(jsonStr.c_str()); if (!arr) { arr = json_object_new_array(); } json_object *resp = json_object_new_object(); json_object_object_add(resp, "templates", arr); sendJson(curreq, resp); json_object_put(resp); } // --- Load document from DB --- void webedit::Api::handleLoadDocument(libhttppp::HttpRequest &curreq, Loading
editor/src/webedit_api.h +1 −0 Original line number Diff line number Diff line Loading @@ -93,6 +93,7 @@ namespace webedit { void handleListDocuments(libhttppp::HttpRequest &curreq, const std::string &sessionid); void handleDeleteDocument(libhttppp::HttpRequest &curreq, const std::string &sessionid, const std::string &docId); void handleListTemplates(libhttppp::HttpRequest &curreq, const std::string &sessionid); void handleTemplateBulkGenerate(libhttppp::HttpRequest &curreq, const std::string &sessionid); // Revision handlers Loading
editor/src/webedit_db.cpp +56 −0 Original line number Diff line number Diff line Loading @@ -409,6 +409,62 @@ std::string webedit::Database::listDocuments(const std::string &domain) { return result; } std::string webedit::Database::listDocumentsForOwnerDomain(const std::string &ownerUid, const std::string &domain) { dbpp::SQL sql; dbpp::DBResult res; std::vector<char> escapedUid, escapedDomain; sql << "SELECT id, name, created, modified, group_name FROM documents " << "WHERE owner_uid='" << dbpp::SQL::escaped(escapedUid, ownerUid.c_str()) << "' AND domain='" << dbpp::SQL::escaped(escapedDomain, domain.c_str()) << "' ORDER BY group_name, name, modified DESC;"; int rows = _db->exec(sql, res); json_object *arr = json_object_new_array(); for (int i = 0; i < rows; ++i) { json_object *obj = json_object_new_object(); json_object_object_add(obj, "id", json_object_new_string(res[i][0])); json_object_object_add(obj, "name", json_object_new_string(res[i][1])); json_object_object_add(obj, "created", json_object_new_string(res[i][2])); json_object_object_add(obj, "modified", json_object_new_string(res[i][3])); json_object_object_add(obj, "group", json_object_new_string(res[i][4])); json_object_array_add(arr, obj); } std::string result = json_object_to_json_string_ext(arr, JSON_C_TO_STRING_NOSLASHESCAPE); json_object_put(arr); return result; } bool webedit::Database::findDocumentIdByNameGroup(const std::string &ownerUid, const std::string &domain, const std::string &name, const std::string &group, std::string &docId) { dbpp::SQL sql; dbpp::DBResult res; std::vector<char> escapedUid, escapedDomain, escapedName, escapedGroup; sql << "SELECT id FROM documents WHERE owner_uid='" << dbpp::SQL::escaped(escapedUid, ownerUid.c_str()) << "' AND domain='" << dbpp::SQL::escaped(escapedDomain, domain.c_str()) << "' AND name='" << dbpp::SQL::escaped(escapedName, name.c_str()) << "' AND group_name='" << dbpp::SQL::escaped(escapedGroup, group.c_str()) << "' ORDER BY modified DESC LIMIT 1;"; int rows = _db->exec(sql, res); if (rows < 1) { docId.clear(); return false; } docId = std::string(res[0][0]); return true; } std::string webedit::Database::listRevisions(const std::string &docId) { dbpp::SQL sql; dbpp::DBResult res; Loading