Loading editor/src/webedit_db.cpp +74 −60 Original line number Diff line number Diff line Loading @@ -71,6 +71,20 @@ webedit::Database::Database(const std::string &driver, const std::string &connec webedit::Database::~Database() { } int webedit::Database::execWithRetry(const dbpp::SQL &sql, dbpp::DBResult &res) { try { return _db->exec(sql, res); } catch (const std::exception &e) { if (_db->isConnected()) { throw; } std::cerr << "webedit::Database: DB connection lost (" << e.what() << "), reconnecting..." << std::endl; _db->reset(); return _db->exec(sql, res); } } void webedit::Database::initTables() { dbpp::SQL sql; dbpp::DBResult res; Loading @@ -91,7 +105,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -103,7 +117,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -115,7 +129,7 @@ void webedit::Database::initTables() { << "FOREIGN KEY (sessionid) REFERENCES session (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -132,7 +146,7 @@ void webedit::Database::initTables() { << "FOREIGN KEY (document_id) REFERENCES documents (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -144,7 +158,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -161,7 +175,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -173,7 +187,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (owner_uid)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -190,7 +204,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -207,7 +221,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -224,7 +238,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -241,7 +255,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); // Schema migrations — add columns introduced after initial release. // ALTER TABLE ADD COLUMN fails if the column already exists; swallow those errors. Loading @@ -249,7 +263,7 @@ void webedit::Database::initTables() { try { sql.clear(); res.clear(); sql << stmt; _db->exec(sql, res); execWithRetry(sql, res); } catch (...) {} }; Loading @@ -269,7 +283,7 @@ void webedit::Database::initTables() { try { sql.clear(); res.clear(); sql << stmt; _db->exec(sql, res); execWithRetry(sql, res); } catch (...) {} }; Loading Loading @@ -301,7 +315,7 @@ std::string webedit::Database::getOption(const std::string &key, sql << "SELECT value FROM options WHERE " << dbpp::quoteIdentifier(*_db, "key") << "='" << dbpp::SQL::escaped(escapedKey, key.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return defaultVal; return res[0][0]; Loading @@ -316,7 +330,7 @@ void webedit::Database::setOption(const std::string &key, const std::string &val sql << "SELECT value FROM options WHERE " << keyColumn << "='" << dbpp::SQL::escaped(escapedKey, key.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); sql.clear(); res.clear(); escapedKey.clear(); Loading @@ -334,7 +348,7 @@ void webedit::Database::setOption(const std::string &key, const std::string &val << "' WHERE " << keyColumn << "='" << dbpp::SQL::escaped(escapedKey, key.c_str()) << "';"; } _db->exec(sql, res); execWithRetry(sql, res); } std::string webedit::Database::saveDocument(const std::string &ownerUid, Loading @@ -357,7 +371,7 @@ std::string webedit::Database::saveDocument(const std::string &ownerUid, << dbpp::SQL::escaped(escapedName, name.c_str()) << "','" << dbpp::SQL::escaped(escapedXml, xml.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); // Create initial revision (revision 1) uuid::uuid revId; Loading @@ -375,7 +389,7 @@ std::string webedit::Database::saveDocument(const std::string &ownerUid, << dbpp::SQL::escaped(escapedXml, xml.c_str()) << "','" << dbpp::SQL::escaped(escapedAuthor, author.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); return docId.c_str(); } Loading @@ -399,7 +413,7 @@ std::string webedit::Database::updateDocument(const std::string &domain, << "' AND domain='" << dbpp::SQL::escaped(escapedDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); // Get next revision number sql.clear(); Loading @@ -409,7 +423,7 @@ std::string webedit::Database::updateDocument(const std::string &domain, sql << "SELECT COALESCE(MAX(revision),0) FROM document_revisions WHERE document_id='" << dbpp::SQL::escaped(escapedId, id.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); int nextRev = 1; if (rows > 0) { try { nextRev = std::stoi(res[0][0]) + 1; } catch (...) {} Loading @@ -433,7 +447,7 @@ std::string webedit::Database::updateDocument(const std::string &domain, << dbpp::SQL::escaped(escapedXml, xml.c_str()) << "','" << dbpp::SQL::escaped(escapedAuthor, author.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); return id; } Loading @@ -451,7 +465,7 @@ bool webedit::Database::loadDocument(const std::string &domain, << "' AND domain='" << dbpp::SQL::escaped(escapedDomain, domain.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return false; Loading @@ -473,7 +487,7 @@ bool webedit::Database::deleteDocument(const std::string &domain, << "' AND domain='" << dbpp::SQL::escaped(escapedDomain, domain.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return false; Loading @@ -484,7 +498,7 @@ bool webedit::Database::deleteDocument(const std::string &domain, // Delete revisions first sql << "DELETE FROM document_revisions WHERE document_id='" << dbpp::SQL::escaped(escapedId, id.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -496,7 +510,7 @@ bool webedit::Database::deleteDocument(const std::string &domain, << "' AND domain='" << dbpp::SQL::escaped(escapedDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -509,7 +523,7 @@ std::string webedit::Database::listDocuments(const std::string &domain) { << dbpp::SQL::escaped(escapedDomain, domain.c_str()) << "' ORDER BY group_name, name, modified DESC;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); Loading Loading @@ -542,7 +556,7 @@ bool webedit::Database::findDocumentIdByNameGroup(const std::string &domain, << "' AND group_name='" << dbpp::SQL::escaped(escapedGroup, group.c_str()) << "' ORDER BY modified DESC LIMIT 1;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) { docId.clear(); return false; Loading @@ -561,7 +575,7 @@ std::string webedit::Database::listRevisions(const std::string &docId) { << dbpp::SQL::escaped(escapedId, docId.c_str()) << "' ORDER BY revision DESC;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); Loading Loading @@ -589,7 +603,7 @@ bool webedit::Database::loadRevision(const std::string &revisionId, sql << "SELECT name, xml FROM document_revisions WHERE id='" << dbpp::SQL::escaped(escapedId, revisionId.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return false; Loading Loading @@ -633,7 +647,7 @@ std::string webedit::Database::saveConnection(const std::string &ownerUid, << dbpp::SQL::escaped(eUrl, url.c_str()) << "'," << (ignoreSsl ? 1 : 0) << ");"; _db->exec(sql, res); execWithRetry(sql, res); return connId.c_str(); } Loading @@ -655,7 +669,7 @@ std::string webedit::Database::updateConnection(const std::string &domain, << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return id; } Loading @@ -669,7 +683,7 @@ bool webedit::Database::deleteConnection(const std::string &domain, << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -683,7 +697,7 @@ std::string webedit::Database::listConnections(const std::string &domain) { << dbpp::SQL::escaped(eDomain, domain.c_str()) << "' ORDER BY group_name, name;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); for (int i = 0; i < rows; ++i) { Loading Loading @@ -715,7 +729,7 @@ bool webedit::Database::getConnection(const std::string &domain, << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return false; name = res[0][0]; Loading Loading @@ -747,7 +761,7 @@ std::string webedit::Database::createApiToken(const std::string &ownerUid, << dbpp::SQL::escaped(eLabel, label.c_str()) << "','" << dbpp::SQL::escaped(eHash, tokenHash.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); return id.c_str(); } Loading @@ -760,7 +774,7 @@ bool webedit::Database::deleteApiToken(const std::string &ownerUid, const std::s << dbpp::SQL::escaped(eId, id.c_str()) << "' AND owner_uid='" << dbpp::SQL::escaped(eOwner, ownerUid.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -773,7 +787,7 @@ std::string webedit::Database::listApiTokens(const std::string &ownerUid) { << dbpp::SQL::escaped(eOwner, ownerUid.c_str()) << "' ORDER BY created DESC;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); for (int i = 0; i < rows; ++i) { Loading Loading @@ -802,7 +816,7 @@ bool webedit::Database::findTokenOwner(const std::string &tokenHash, std::string sql << "SELECT id, owner_uid, domain, authid FROM api_tokens WHERE token_hash='" << dbpp::SQL::escaped(eHash, tokenHash.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return false; Loading @@ -821,7 +835,7 @@ void webedit::Database::touchApiTokenLastUsed(const std::string &tokenId) { sql << "UPDATE api_tokens SET last_used=CURRENT_TIMESTAMP WHERE id='" << dbpp::SQL::escaped(eId, tokenId.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); } // --- AI Chat Messages --- Loading @@ -838,14 +852,14 @@ void webedit::Database::saveChatMessages(const std::string &ownerUid, << "', modified=CURRENT_TIMESTAMP WHERE owner_uid='" << dbpp::SQL::escaped(eUid, ownerUid.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); // Check if row exists sql.clear(); res.clear(); eUid.clear(); sql << "SELECT owner_uid FROM ai_chat_messages WHERE owner_uid='" << dbpp::SQL::escaped(eUid, ownerUid.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) { sql.clear(); res.clear(); Loading @@ -853,7 +867,7 @@ void webedit::Database::saveChatMessages(const std::string &ownerUid, sql << "INSERT INTO ai_chat_messages (owner_uid, messages) VALUES ('" << dbpp::SQL::escaped(eUid, ownerUid.c_str()) << "','" << dbpp::SQL::escaped(eMsg, messagesJson.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); } } Loading @@ -865,7 +879,7 @@ std::string webedit::Database::loadChatMessages(const std::string &ownerUid) { sql << "SELECT messages FROM ai_chat_messages WHERE owner_uid='" << dbpp::SQL::escaped(eUid, ownerUid.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return "[]"; return res[0][0]; } Loading @@ -877,7 +891,7 @@ void webedit::Database::clearChatMessages(const std::string &ownerUid) { sql << "DELETE FROM ai_chat_messages WHERE owner_uid='" << dbpp::SQL::escaped(eUid, ownerUid.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); } // --------------------------------------------------------------------------- Loading @@ -904,7 +918,7 @@ std::string webedit::Database::saveSurvey(const std::string &ownerUid, << dbpp::SQL::escaped(eTitle, title.c_str()) << "','" << dbpp::SQL::escaped(eXml, xml.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); return std::string(id.c_str()); } Loading @@ -923,7 +937,7 @@ bool webedit::Database::updateSurvey(const std::string &domain, const std::strin << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -935,7 +949,7 @@ bool webedit::Database::deleteSurvey(const std::string &domain, const std::strin sql << "DELETE FROM survey_templates WHERE id='" << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -949,7 +963,7 @@ std::string webedit::Database::listSurveys(const std::string &domain) { << dbpp::SQL::escaped(eDomain, domain.c_str()) << "' ORDER BY name;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); for (int i = 0; i < rows; ++i) { json_object *obj = json_object_new_object(); Loading @@ -974,7 +988,7 @@ std::string webedit::Database::listSurveyNames(const std::string &domain) { sql << "SELECT id, name FROM survey_templates WHERE domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "' ORDER BY name;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); for (int i = 0; i < rows; ++i) { json_object *obj = json_object_new_object(); Loading @@ -998,7 +1012,7 @@ std::string webedit::Database::getSurvey(const std::string &domain, const std::s << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return "{}"; json_object *survey = json_object_new_object(); Loading Loading @@ -1038,7 +1052,7 @@ std::string webedit::Database::saveElementTemplate(const std::string &ownerUid, << dbpp::SQL::escaped(eRootType, rootType.c_str()) << "','" << dbpp::SQL::escaped(eXml, xml.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); return std::string(id.c_str()); } Loading @@ -1054,7 +1068,7 @@ bool webedit::Database::renameElementTemplate(const std::string &domain, const s << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -1066,7 +1080,7 @@ bool webedit::Database::deleteElementTemplate(const std::string &domain, const s sql << "DELETE FROM element_templates WHERE id='" << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -1080,7 +1094,7 @@ std::string webedit::Database::listElementTemplates(const std::string &domain) { << dbpp::SQL::escaped(eDomain, domain.c_str()) << "' ORDER BY name;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); for (int i = 0; i < rows; ++i) { json_object *obj = json_object_new_object(); Loading @@ -1107,7 +1121,7 @@ std::string webedit::Database::getElementTemplate(const std::string &domain, con << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return "{}"; json_object *tpl = json_object_new_object(); Loading Loading @@ -1147,7 +1161,7 @@ std::string webedit::Database::saveIcon(const std::string &ownerUid, << dbpp::SQL::escaped(eTags, tags.c_str()) << "','" << dbpp::SQL::escaped(eSvg, svg.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); return std::string(id.c_str()); } Loading @@ -1166,7 +1180,7 @@ bool webedit::Database::updateIcon(const std::string &domain, const std::string << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -1178,7 +1192,7 @@ bool webedit::Database::deleteIcon(const std::string &domain, const std::string sql << "DELETE FROM icons WHERE id='" << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -1192,7 +1206,7 @@ std::string webedit::Database::listIcons(const std::string &domain) { << dbpp::SQL::escaped(eDomain, domain.c_str()) << "' ORDER BY name;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); for (int i = 0; i < rows; ++i) { json_object *obj = json_object_new_object(); Loading Loading @@ -1220,7 +1234,7 @@ std::string webedit::Database::getIcon(const std::string &domain, const std::str << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return "{}"; json_object *icon = json_object_new_object(); Loading editor/src/webedit_db.h +5 −0 Original line number Diff line number Diff line Loading @@ -138,6 +138,11 @@ namespace webedit { private: std::string _driver; std::unique_ptr<dbpp::ReplicatedDatabase> _db; // Wraps _db->exec(): on failure, checks whether the connection // dropped and if so reconnects and retries the query once before // giving up (mirrors the retry the main blogi app does per-request). int execWithRetry(const dbpp::SQL &sql, dbpp::DBResult &res); }; } Loading
editor/src/webedit_db.cpp +74 −60 Original line number Diff line number Diff line Loading @@ -71,6 +71,20 @@ webedit::Database::Database(const std::string &driver, const std::string &connec webedit::Database::~Database() { } int webedit::Database::execWithRetry(const dbpp::SQL &sql, dbpp::DBResult &res) { try { return _db->exec(sql, res); } catch (const std::exception &e) { if (_db->isConnected()) { throw; } std::cerr << "webedit::Database: DB connection lost (" << e.what() << "), reconnecting..." << std::endl; _db->reset(); return _db->exec(sql, res); } } void webedit::Database::initTables() { dbpp::SQL sql; dbpp::DBResult res; Loading @@ -91,7 +105,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -103,7 +117,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -115,7 +129,7 @@ void webedit::Database::initTables() { << "FOREIGN KEY (sessionid) REFERENCES session (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -132,7 +146,7 @@ void webedit::Database::initTables() { << "FOREIGN KEY (document_id) REFERENCES documents (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -144,7 +158,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -161,7 +175,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -173,7 +187,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (owner_uid)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -190,7 +204,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -207,7 +221,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -224,7 +238,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -241,7 +255,7 @@ void webedit::Database::initTables() { << "PRIMARY KEY (id)" << ");"; _db->exec(sql, res); execWithRetry(sql, res); // Schema migrations — add columns introduced after initial release. // ALTER TABLE ADD COLUMN fails if the column already exists; swallow those errors. Loading @@ -249,7 +263,7 @@ void webedit::Database::initTables() { try { sql.clear(); res.clear(); sql << stmt; _db->exec(sql, res); execWithRetry(sql, res); } catch (...) {} }; Loading @@ -269,7 +283,7 @@ void webedit::Database::initTables() { try { sql.clear(); res.clear(); sql << stmt; _db->exec(sql, res); execWithRetry(sql, res); } catch (...) {} }; Loading Loading @@ -301,7 +315,7 @@ std::string webedit::Database::getOption(const std::string &key, sql << "SELECT value FROM options WHERE " << dbpp::quoteIdentifier(*_db, "key") << "='" << dbpp::SQL::escaped(escapedKey, key.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return defaultVal; return res[0][0]; Loading @@ -316,7 +330,7 @@ void webedit::Database::setOption(const std::string &key, const std::string &val sql << "SELECT value FROM options WHERE " << keyColumn << "='" << dbpp::SQL::escaped(escapedKey, key.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); sql.clear(); res.clear(); escapedKey.clear(); Loading @@ -334,7 +348,7 @@ void webedit::Database::setOption(const std::string &key, const std::string &val << "' WHERE " << keyColumn << "='" << dbpp::SQL::escaped(escapedKey, key.c_str()) << "';"; } _db->exec(sql, res); execWithRetry(sql, res); } std::string webedit::Database::saveDocument(const std::string &ownerUid, Loading @@ -357,7 +371,7 @@ std::string webedit::Database::saveDocument(const std::string &ownerUid, << dbpp::SQL::escaped(escapedName, name.c_str()) << "','" << dbpp::SQL::escaped(escapedXml, xml.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); // Create initial revision (revision 1) uuid::uuid revId; Loading @@ -375,7 +389,7 @@ std::string webedit::Database::saveDocument(const std::string &ownerUid, << dbpp::SQL::escaped(escapedXml, xml.c_str()) << "','" << dbpp::SQL::escaped(escapedAuthor, author.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); return docId.c_str(); } Loading @@ -399,7 +413,7 @@ std::string webedit::Database::updateDocument(const std::string &domain, << "' AND domain='" << dbpp::SQL::escaped(escapedDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); // Get next revision number sql.clear(); Loading @@ -409,7 +423,7 @@ std::string webedit::Database::updateDocument(const std::string &domain, sql << "SELECT COALESCE(MAX(revision),0) FROM document_revisions WHERE document_id='" << dbpp::SQL::escaped(escapedId, id.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); int nextRev = 1; if (rows > 0) { try { nextRev = std::stoi(res[0][0]) + 1; } catch (...) {} Loading @@ -433,7 +447,7 @@ std::string webedit::Database::updateDocument(const std::string &domain, << dbpp::SQL::escaped(escapedXml, xml.c_str()) << "','" << dbpp::SQL::escaped(escapedAuthor, author.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); return id; } Loading @@ -451,7 +465,7 @@ bool webedit::Database::loadDocument(const std::string &domain, << "' AND domain='" << dbpp::SQL::escaped(escapedDomain, domain.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return false; Loading @@ -473,7 +487,7 @@ bool webedit::Database::deleteDocument(const std::string &domain, << "' AND domain='" << dbpp::SQL::escaped(escapedDomain, domain.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return false; Loading @@ -484,7 +498,7 @@ bool webedit::Database::deleteDocument(const std::string &domain, // Delete revisions first sql << "DELETE FROM document_revisions WHERE document_id='" << dbpp::SQL::escaped(escapedId, id.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); sql.clear(); res.clear(); Loading @@ -496,7 +510,7 @@ bool webedit::Database::deleteDocument(const std::string &domain, << "' AND domain='" << dbpp::SQL::escaped(escapedDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -509,7 +523,7 @@ std::string webedit::Database::listDocuments(const std::string &domain) { << dbpp::SQL::escaped(escapedDomain, domain.c_str()) << "' ORDER BY group_name, name, modified DESC;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); Loading Loading @@ -542,7 +556,7 @@ bool webedit::Database::findDocumentIdByNameGroup(const std::string &domain, << "' AND group_name='" << dbpp::SQL::escaped(escapedGroup, group.c_str()) << "' ORDER BY modified DESC LIMIT 1;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) { docId.clear(); return false; Loading @@ -561,7 +575,7 @@ std::string webedit::Database::listRevisions(const std::string &docId) { << dbpp::SQL::escaped(escapedId, docId.c_str()) << "' ORDER BY revision DESC;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); Loading Loading @@ -589,7 +603,7 @@ bool webedit::Database::loadRevision(const std::string &revisionId, sql << "SELECT name, xml FROM document_revisions WHERE id='" << dbpp::SQL::escaped(escapedId, revisionId.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return false; Loading Loading @@ -633,7 +647,7 @@ std::string webedit::Database::saveConnection(const std::string &ownerUid, << dbpp::SQL::escaped(eUrl, url.c_str()) << "'," << (ignoreSsl ? 1 : 0) << ");"; _db->exec(sql, res); execWithRetry(sql, res); return connId.c_str(); } Loading @@ -655,7 +669,7 @@ std::string webedit::Database::updateConnection(const std::string &domain, << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return id; } Loading @@ -669,7 +683,7 @@ bool webedit::Database::deleteConnection(const std::string &domain, << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -683,7 +697,7 @@ std::string webedit::Database::listConnections(const std::string &domain) { << dbpp::SQL::escaped(eDomain, domain.c_str()) << "' ORDER BY group_name, name;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); for (int i = 0; i < rows; ++i) { Loading Loading @@ -715,7 +729,7 @@ bool webedit::Database::getConnection(const std::string &domain, << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return false; name = res[0][0]; Loading Loading @@ -747,7 +761,7 @@ std::string webedit::Database::createApiToken(const std::string &ownerUid, << dbpp::SQL::escaped(eLabel, label.c_str()) << "','" << dbpp::SQL::escaped(eHash, tokenHash.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); return id.c_str(); } Loading @@ -760,7 +774,7 @@ bool webedit::Database::deleteApiToken(const std::string &ownerUid, const std::s << dbpp::SQL::escaped(eId, id.c_str()) << "' AND owner_uid='" << dbpp::SQL::escaped(eOwner, ownerUid.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -773,7 +787,7 @@ std::string webedit::Database::listApiTokens(const std::string &ownerUid) { << dbpp::SQL::escaped(eOwner, ownerUid.c_str()) << "' ORDER BY created DESC;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); for (int i = 0; i < rows; ++i) { Loading Loading @@ -802,7 +816,7 @@ bool webedit::Database::findTokenOwner(const std::string &tokenHash, std::string sql << "SELECT id, owner_uid, domain, authid FROM api_tokens WHERE token_hash='" << dbpp::SQL::escaped(eHash, tokenHash.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return false; Loading @@ -821,7 +835,7 @@ void webedit::Database::touchApiTokenLastUsed(const std::string &tokenId) { sql << "UPDATE api_tokens SET last_used=CURRENT_TIMESTAMP WHERE id='" << dbpp::SQL::escaped(eId, tokenId.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); } // --- AI Chat Messages --- Loading @@ -838,14 +852,14 @@ void webedit::Database::saveChatMessages(const std::string &ownerUid, << "', modified=CURRENT_TIMESTAMP WHERE owner_uid='" << dbpp::SQL::escaped(eUid, ownerUid.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); // Check if row exists sql.clear(); res.clear(); eUid.clear(); sql << "SELECT owner_uid FROM ai_chat_messages WHERE owner_uid='" << dbpp::SQL::escaped(eUid, ownerUid.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) { sql.clear(); res.clear(); Loading @@ -853,7 +867,7 @@ void webedit::Database::saveChatMessages(const std::string &ownerUid, sql << "INSERT INTO ai_chat_messages (owner_uid, messages) VALUES ('" << dbpp::SQL::escaped(eUid, ownerUid.c_str()) << "','" << dbpp::SQL::escaped(eMsg, messagesJson.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); } } Loading @@ -865,7 +879,7 @@ std::string webedit::Database::loadChatMessages(const std::string &ownerUid) { sql << "SELECT messages FROM ai_chat_messages WHERE owner_uid='" << dbpp::SQL::escaped(eUid, ownerUid.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return "[]"; return res[0][0]; } Loading @@ -877,7 +891,7 @@ void webedit::Database::clearChatMessages(const std::string &ownerUid) { sql << "DELETE FROM ai_chat_messages WHERE owner_uid='" << dbpp::SQL::escaped(eUid, ownerUid.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); } // --------------------------------------------------------------------------- Loading @@ -904,7 +918,7 @@ std::string webedit::Database::saveSurvey(const std::string &ownerUid, << dbpp::SQL::escaped(eTitle, title.c_str()) << "','" << dbpp::SQL::escaped(eXml, xml.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); return std::string(id.c_str()); } Loading @@ -923,7 +937,7 @@ bool webedit::Database::updateSurvey(const std::string &domain, const std::strin << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -935,7 +949,7 @@ bool webedit::Database::deleteSurvey(const std::string &domain, const std::strin sql << "DELETE FROM survey_templates WHERE id='" << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -949,7 +963,7 @@ std::string webedit::Database::listSurveys(const std::string &domain) { << dbpp::SQL::escaped(eDomain, domain.c_str()) << "' ORDER BY name;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); for (int i = 0; i < rows; ++i) { json_object *obj = json_object_new_object(); Loading @@ -974,7 +988,7 @@ std::string webedit::Database::listSurveyNames(const std::string &domain) { sql << "SELECT id, name FROM survey_templates WHERE domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "' ORDER BY name;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); for (int i = 0; i < rows; ++i) { json_object *obj = json_object_new_object(); Loading @@ -998,7 +1012,7 @@ std::string webedit::Database::getSurvey(const std::string &domain, const std::s << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return "{}"; json_object *survey = json_object_new_object(); Loading Loading @@ -1038,7 +1052,7 @@ std::string webedit::Database::saveElementTemplate(const std::string &ownerUid, << dbpp::SQL::escaped(eRootType, rootType.c_str()) << "','" << dbpp::SQL::escaped(eXml, xml.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); return std::string(id.c_str()); } Loading @@ -1054,7 +1068,7 @@ bool webedit::Database::renameElementTemplate(const std::string &domain, const s << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -1066,7 +1080,7 @@ bool webedit::Database::deleteElementTemplate(const std::string &domain, const s sql << "DELETE FROM element_templates WHERE id='" << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -1080,7 +1094,7 @@ std::string webedit::Database::listElementTemplates(const std::string &domain) { << dbpp::SQL::escaped(eDomain, domain.c_str()) << "' ORDER BY name;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); for (int i = 0; i < rows; ++i) { json_object *obj = json_object_new_object(); Loading @@ -1107,7 +1121,7 @@ std::string webedit::Database::getElementTemplate(const std::string &domain, con << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return "{}"; json_object *tpl = json_object_new_object(); Loading Loading @@ -1147,7 +1161,7 @@ std::string webedit::Database::saveIcon(const std::string &ownerUid, << dbpp::SQL::escaped(eTags, tags.c_str()) << "','" << dbpp::SQL::escaped(eSvg, svg.c_str()) << "');"; _db->exec(sql, res); execWithRetry(sql, res); return std::string(id.c_str()); } Loading @@ -1166,7 +1180,7 @@ bool webedit::Database::updateIcon(const std::string &domain, const std::string << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -1178,7 +1192,7 @@ bool webedit::Database::deleteIcon(const std::string &domain, const std::string sql << "DELETE FROM icons WHERE id='" << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; _db->exec(sql, res); execWithRetry(sql, res); return true; } Loading @@ -1192,7 +1206,7 @@ std::string webedit::Database::listIcons(const std::string &domain) { << dbpp::SQL::escaped(eDomain, domain.c_str()) << "' ORDER BY name;"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); json_object *arr = json_object_new_array(); for (int i = 0; i < rows; ++i) { json_object *obj = json_object_new_object(); Loading Loading @@ -1220,7 +1234,7 @@ std::string webedit::Database::getIcon(const std::string &domain, const std::str << dbpp::SQL::escaped(eId, id.c_str()) << "' AND domain='" << dbpp::SQL::escaped(eDomain, domain.c_str()) << "';"; int rows = _db->exec(sql, res); int rows = execWithRetry(sql, res); if (rows < 1) return "{}"; json_object *icon = json_object_new_object(); Loading
editor/src/webedit_db.h +5 −0 Original line number Diff line number Diff line Loading @@ -138,6 +138,11 @@ namespace webedit { private: std::string _driver; std::unique_ptr<dbpp::ReplicatedDatabase> _db; // Wraps _db->exec(): on failure, checks whether the connection // dropped and if so reconnects and retries the query once before // giving up (mirrors the retry the main blogi app does per-request). int execWithRetry(const dbpp::SQL &sql, dbpp::DBResult &res); }; }