Loading editor/widgets/googlereviews/googlereviews.cpp +6 −2 Original line number Diff line number Diff line Loading @@ -95,6 +95,7 @@ namespace blogi { '#'+uid+' .gr-track{display:flex;transition:transform .4s ease;gap:16px;}'+ '#'+uid+' .gr-card{flex:0 0 calc(33.333% - 11px);max-width:calc(33.333% - 11px);background:#f9fafb;border-radius:14px;padding:16px;display:flex;flex-direction:row;gap:12px;border:1px solid rgba(148,163,184,.3);box-sizing:border-box;}'+ '#'+uid+' .gr-av{width:40px;height:40px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:1.05em;color:#fff;}'+ '#'+uid+' .gr-av-img{object-fit:cover;background:#e5e7eb;}'+ '#'+uid+' .gr-bd{flex:1;font-size:.95em;color:#111827;}'+ '#'+uid+' .gr-name{font-weight:600;margin-bottom:3px;}'+ '#'+uid+' .gr-rstars{color:#fbbf24;font-size:.9em;margin-bottom:4px;}'+ Loading Loading @@ -160,14 +161,17 @@ namespace blogi { var track=root.querySelector('.gr-track'); reviews.forEach(function(r){ var name=r.author_name||'Anonym'; var init=name.charAt(0).toUpperCase(); var full=String(r.text||''); var isLong=full.length>100; var short=isLong?full.slice(0,100)+'…':full; var photoUrl=String(r.author_photo_url||''); var avatarHtml=photoUrl ? '<img class="gr-av gr-av-img" src="'+photoUrl+'" alt="'+name+'" loading="lazy">' : '<div class="gr-av" style="background:'+avColor(name)+'">'+name.charAt(0).toUpperCase()+'</div>'; var card=document.createElement('div'); card.className='gr-card'; card.innerHTML= '<div class="gr-av" style="background:'+avColor(name)+'">'+init+'</div>'+ avatarHtml+ '<div class="gr-bd" data-full="'+encodeURIComponent(full)+'">'+ '<div class="gr-name">'+name+'</div>'+ '<div class="gr-rstars">'+starsHtml(r.rating||5)+'</div>'+ Loading plugins/google/google.cpp +28 −13 Original line number Diff line number Diff line Loading @@ -50,10 +50,17 @@ namespace blogi { << "rating integer NOT NULL," << "text text," << "time character varying(64)," << "author_photo_url text," << "approved boolean DEFAULT false" << ");"; Args->database[0]->exec(sql, res); // Migrate existing installations that lack the photo column dbpp::SQL sqlmig; dbpp::DBResult resmig; sqlmig << "ALTER TABLE google_reviews ADD COLUMN IF NOT EXISTS author_photo_url text;"; try { Args->database[0]->exec(sqlmig, resmig); } catch(...) {} try { getConfig(*Args->database[0], "GOOGLE_PLACES_API_KEY", _apiKey); } catch(...) {} try { getConfig(*Args->database[0], "GOOGLE_PLACE_ID", _placeId); } catch(...) {} try { getConfig(*Args->database[0], "GOOGLE_REVIEW_NOTIFY_EMAIL", _notifyEmail); } catch(...) {} Loading Loading @@ -237,7 +244,7 @@ namespace blogi { if(req.getRequestURL().compare(0, listUrl.length(), listUrl) == 0 && req.getRequestType() == GETREQUEST) { dbpp::SQL sql; dbpp::DBResult res; sql << "SELECT author_name, rating, text, time FROM google_reviews WHERE approved=true;"; sql << "SELECT author_name, rating, text, time, author_photo_url FROM google_reviews WHERE approved=true;"; int ncount = Args->database[tid]->exec(sql, res); json_object *list = json_object_new_array(); Loading @@ -245,8 +252,9 @@ namespace blogi { json_object *rev = json_object_new_object(); json_object_object_add(rev, "author_name", json_object_new_string(res[i][0])); json_object_object_add(rev, "rating", json_object_new_int(atoi(res[i][1]))); json_object_object_add(rev, "text", json_object_new_string(res[i][2])); json_object_object_add(rev, "time", json_object_new_string(res[i][3])); json_object_object_add(rev, "text", json_object_new_string(res[i][2] ? res[i][2] : "")); json_object_object_add(rev, "time", json_object_new_string(res[i][3] ? res[i][3] : "")); json_object_object_add(rev, "author_photo_url", json_object_new_string(res[i][4] ? res[i][4] : "")); json_object_array_add(list, rev); } const char* output = json_object_to_json_string(list); Loading @@ -273,17 +281,19 @@ namespace blogi { for(int i = 0; i < len; ++i) { json_object* jrev = json_object_array_get_idx(jobj, i); json_object *jauthor, *jrating, *jtext, *jtime; std::string author, text, time; json_object *jauthor, *jrating, *jtext, *jtime, *jphoto; std::string author, text, time, photoUrl; int rating = 5; if (json_object_object_get_ex(jrev, "author_name", &jauthor)) author = json_object_get_string(jauthor); if (json_object_object_get_ex(jrev, "rating", &jrating)) rating = json_object_get_int(jrating); if (json_object_object_get_ex(jrev, "text", &jtext)) text = json_object_get_string(jtext); if (json_object_object_get_ex(jrev, "time", &jtime)) time = json_object_get_string(jtime); if (json_object_object_get_ex(jrev, "author_photo_url", &jphoto)) photoUrl = json_object_get_string(jphoto); if (json_object_object_get_ex(jrev, "profile_photo_url", &jphoto)) photoUrl = json_object_get_string(jphoto); if(!author.empty()) _processReview(tid, author, rating, text, time); _processReview(tid, author, rating, text, time, photoUrl); } } Loading Loading @@ -328,7 +338,7 @@ namespace blogi { void _fetchFromGoogle(int tid) { try { std::string path = "/maps/api/place/details/json?place_id=" + _placeId + "&fields=reviews&key=" + _apiKey; + "&fields=reviews%2Creviews.profile_photo_url&key=" + _apiKey; libhttppp::HttpUrl apiUrl("https://maps.googleapis.com" + path); libhttppp::HttpClient client(apiUrl); client.setTimeout(15); Loading @@ -353,8 +363,8 @@ namespace blogi { int len = json_object_array_length(jreviews); for (int i = 0; i < len; ++i) { json_object *jrev = json_object_array_get_idx(jreviews, i); json_object *jauthor, *jrating, *jtext, *jtime; std::string author, text, timeStr; json_object *jauthor, *jrating, *jtext, *jtime, *jphoto; std::string author, text, timeStr, photoUrl; int rating = 5; if (json_object_object_get_ex(jrev, "author_name", &jauthor)) Loading @@ -365,9 +375,11 @@ namespace blogi { text = json_object_get_string(jtext); if (json_object_object_get_ex(jrev, "time", &jtime)) timeStr = std::to_string(json_object_get_int64(jtime)); if (json_object_object_get_ex(jrev, "profile_photo_url", &jphoto)) photoUrl = json_object_get_string(jphoto); if (!author.empty()) _processReview(tid, author, rating, text, timeStr); _processReview(tid, author, rating, text, timeStr, photoUrl); } } } Loading @@ -380,7 +392,8 @@ namespace blogi { } void _processReview(int tid, const std::string &author, int rating, const std::string &text, const std::string &time) { const std::string &text, const std::string &time, const std::string &photoUrl = "") { std::vector<char> buf; dbpp::SQL sqlexist; dbpp::DBResult res; Loading @@ -396,12 +409,14 @@ namespace blogi { sql << "UPDATE google_reviews SET rating=" << rating << ", text='" << dbpp::SQL::escaped(buf, text.c_str()) << "'" << ", time='" << dbpp::SQL::escaped(buf, time.c_str()) << "'" << ", author_photo_url='" << dbpp::SQL::escaped(buf, photoUrl.c_str()) << "'" << " WHERE author_name='" << dbpp::SQL::escaped(buf, author.c_str()) << "';"; } else { sql << "INSERT INTO google_reviews (author_name, rating, text, time, approved) VALUES ('" sql << "INSERT INTO google_reviews (author_name, rating, text, time, author_photo_url, approved) VALUES ('" << dbpp::SQL::escaped(buf, author.c_str()) << "', " << rating << ", '" << dbpp::SQL::escaped(buf, text.c_str()) << "', '" << dbpp::SQL::escaped(buf, time.c_str()) << "', false);"; << dbpp::SQL::escaped(buf, time.c_str()) << "', '" << dbpp::SQL::escaped(buf, photoUrl.c_str()) << "', false);"; } Args->database[tid]->exec(sql, res); Loading Loading
editor/widgets/googlereviews/googlereviews.cpp +6 −2 Original line number Diff line number Diff line Loading @@ -95,6 +95,7 @@ namespace blogi { '#'+uid+' .gr-track{display:flex;transition:transform .4s ease;gap:16px;}'+ '#'+uid+' .gr-card{flex:0 0 calc(33.333% - 11px);max-width:calc(33.333% - 11px);background:#f9fafb;border-radius:14px;padding:16px;display:flex;flex-direction:row;gap:12px;border:1px solid rgba(148,163,184,.3);box-sizing:border-box;}'+ '#'+uid+' .gr-av{width:40px;height:40px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:1.05em;color:#fff;}'+ '#'+uid+' .gr-av-img{object-fit:cover;background:#e5e7eb;}'+ '#'+uid+' .gr-bd{flex:1;font-size:.95em;color:#111827;}'+ '#'+uid+' .gr-name{font-weight:600;margin-bottom:3px;}'+ '#'+uid+' .gr-rstars{color:#fbbf24;font-size:.9em;margin-bottom:4px;}'+ Loading Loading @@ -160,14 +161,17 @@ namespace blogi { var track=root.querySelector('.gr-track'); reviews.forEach(function(r){ var name=r.author_name||'Anonym'; var init=name.charAt(0).toUpperCase(); var full=String(r.text||''); var isLong=full.length>100; var short=isLong?full.slice(0,100)+'…':full; var photoUrl=String(r.author_photo_url||''); var avatarHtml=photoUrl ? '<img class="gr-av gr-av-img" src="'+photoUrl+'" alt="'+name+'" loading="lazy">' : '<div class="gr-av" style="background:'+avColor(name)+'">'+name.charAt(0).toUpperCase()+'</div>'; var card=document.createElement('div'); card.className='gr-card'; card.innerHTML= '<div class="gr-av" style="background:'+avColor(name)+'">'+init+'</div>'+ avatarHtml+ '<div class="gr-bd" data-full="'+encodeURIComponent(full)+'">'+ '<div class="gr-name">'+name+'</div>'+ '<div class="gr-rstars">'+starsHtml(r.rating||5)+'</div>'+ Loading
plugins/google/google.cpp +28 −13 Original line number Diff line number Diff line Loading @@ -50,10 +50,17 @@ namespace blogi { << "rating integer NOT NULL," << "text text," << "time character varying(64)," << "author_photo_url text," << "approved boolean DEFAULT false" << ");"; Args->database[0]->exec(sql, res); // Migrate existing installations that lack the photo column dbpp::SQL sqlmig; dbpp::DBResult resmig; sqlmig << "ALTER TABLE google_reviews ADD COLUMN IF NOT EXISTS author_photo_url text;"; try { Args->database[0]->exec(sqlmig, resmig); } catch(...) {} try { getConfig(*Args->database[0], "GOOGLE_PLACES_API_KEY", _apiKey); } catch(...) {} try { getConfig(*Args->database[0], "GOOGLE_PLACE_ID", _placeId); } catch(...) {} try { getConfig(*Args->database[0], "GOOGLE_REVIEW_NOTIFY_EMAIL", _notifyEmail); } catch(...) {} Loading Loading @@ -237,7 +244,7 @@ namespace blogi { if(req.getRequestURL().compare(0, listUrl.length(), listUrl) == 0 && req.getRequestType() == GETREQUEST) { dbpp::SQL sql; dbpp::DBResult res; sql << "SELECT author_name, rating, text, time FROM google_reviews WHERE approved=true;"; sql << "SELECT author_name, rating, text, time, author_photo_url FROM google_reviews WHERE approved=true;"; int ncount = Args->database[tid]->exec(sql, res); json_object *list = json_object_new_array(); Loading @@ -245,8 +252,9 @@ namespace blogi { json_object *rev = json_object_new_object(); json_object_object_add(rev, "author_name", json_object_new_string(res[i][0])); json_object_object_add(rev, "rating", json_object_new_int(atoi(res[i][1]))); json_object_object_add(rev, "text", json_object_new_string(res[i][2])); json_object_object_add(rev, "time", json_object_new_string(res[i][3])); json_object_object_add(rev, "text", json_object_new_string(res[i][2] ? res[i][2] : "")); json_object_object_add(rev, "time", json_object_new_string(res[i][3] ? res[i][3] : "")); json_object_object_add(rev, "author_photo_url", json_object_new_string(res[i][4] ? res[i][4] : "")); json_object_array_add(list, rev); } const char* output = json_object_to_json_string(list); Loading @@ -273,17 +281,19 @@ namespace blogi { for(int i = 0; i < len; ++i) { json_object* jrev = json_object_array_get_idx(jobj, i); json_object *jauthor, *jrating, *jtext, *jtime; std::string author, text, time; json_object *jauthor, *jrating, *jtext, *jtime, *jphoto; std::string author, text, time, photoUrl; int rating = 5; if (json_object_object_get_ex(jrev, "author_name", &jauthor)) author = json_object_get_string(jauthor); if (json_object_object_get_ex(jrev, "rating", &jrating)) rating = json_object_get_int(jrating); if (json_object_object_get_ex(jrev, "text", &jtext)) text = json_object_get_string(jtext); if (json_object_object_get_ex(jrev, "time", &jtime)) time = json_object_get_string(jtime); if (json_object_object_get_ex(jrev, "author_photo_url", &jphoto)) photoUrl = json_object_get_string(jphoto); if (json_object_object_get_ex(jrev, "profile_photo_url", &jphoto)) photoUrl = json_object_get_string(jphoto); if(!author.empty()) _processReview(tid, author, rating, text, time); _processReview(tid, author, rating, text, time, photoUrl); } } Loading Loading @@ -328,7 +338,7 @@ namespace blogi { void _fetchFromGoogle(int tid) { try { std::string path = "/maps/api/place/details/json?place_id=" + _placeId + "&fields=reviews&key=" + _apiKey; + "&fields=reviews%2Creviews.profile_photo_url&key=" + _apiKey; libhttppp::HttpUrl apiUrl("https://maps.googleapis.com" + path); libhttppp::HttpClient client(apiUrl); client.setTimeout(15); Loading @@ -353,8 +363,8 @@ namespace blogi { int len = json_object_array_length(jreviews); for (int i = 0; i < len; ++i) { json_object *jrev = json_object_array_get_idx(jreviews, i); json_object *jauthor, *jrating, *jtext, *jtime; std::string author, text, timeStr; json_object *jauthor, *jrating, *jtext, *jtime, *jphoto; std::string author, text, timeStr, photoUrl; int rating = 5; if (json_object_object_get_ex(jrev, "author_name", &jauthor)) Loading @@ -365,9 +375,11 @@ namespace blogi { text = json_object_get_string(jtext); if (json_object_object_get_ex(jrev, "time", &jtime)) timeStr = std::to_string(json_object_get_int64(jtime)); if (json_object_object_get_ex(jrev, "profile_photo_url", &jphoto)) photoUrl = json_object_get_string(jphoto); if (!author.empty()) _processReview(tid, author, rating, text, timeStr); _processReview(tid, author, rating, text, timeStr, photoUrl); } } } Loading @@ -380,7 +392,8 @@ namespace blogi { } void _processReview(int tid, const std::string &author, int rating, const std::string &text, const std::string &time) { const std::string &text, const std::string &time, const std::string &photoUrl = "") { std::vector<char> buf; dbpp::SQL sqlexist; dbpp::DBResult res; Loading @@ -396,12 +409,14 @@ namespace blogi { sql << "UPDATE google_reviews SET rating=" << rating << ", text='" << dbpp::SQL::escaped(buf, text.c_str()) << "'" << ", time='" << dbpp::SQL::escaped(buf, time.c_str()) << "'" << ", author_photo_url='" << dbpp::SQL::escaped(buf, photoUrl.c_str()) << "'" << " WHERE author_name='" << dbpp::SQL::escaped(buf, author.c_str()) << "';"; } else { sql << "INSERT INTO google_reviews (author_name, rating, text, time, approved) VALUES ('" sql << "INSERT INTO google_reviews (author_name, rating, text, time, author_photo_url, approved) VALUES ('" << dbpp::SQL::escaped(buf, author.c_str()) << "', " << rating << ", '" << dbpp::SQL::escaped(buf, text.c_str()) << "', '" << dbpp::SQL::escaped(buf, time.c_str()) << "', false);"; << dbpp::SQL::escaped(buf, time.c_str()) << "', '" << dbpp::SQL::escaped(buf, photoUrl.c_str()) << "', false);"; } Args->database[tid]->exec(sql, res); Loading