Commit 0ef9d8b5 authored by Jan Köster's avatar Jan Köster
Browse files

test

parent 951070a8
Loading
Loading
Loading
Loading
+169 −24
Changes for plugins/google/google.cpp: 169 added lines, 24 removed lines.
Original line number Diff line number Diff line
@@ -3,6 +3,10 @@
#include <regex>
#include <chrono>
#include <string>
#include <thread>
#include <atomic>
#include <mutex>
#include <condition_variable>

#include <htmlpp/html.h>
#include <htmlpp/exception.h>
@@ -24,6 +28,13 @@
namespace blogi {
    class GooglePlugin : public PluginApi {
    public:
        ~GooglePlugin() {
            _stop.store(true);
            _cv.notify_all();
            if (_syncThread.joinable())
                _syncThread.join();
        }

        void initPlugin() override {
            registerGPO("gpo-google-plugin-settings",
                        "Blogi: Google Settings",
@@ -42,6 +53,13 @@ namespace blogi {
                << ");";
            Args->database[0]->exec(sql, res);

            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(...) {}
            try { getConfig(*Args->database[0], "GOOGLE_REVIEW_SYNC_DAYS", _syncDays); } catch(...) {}

            _syncThread = std::thread(&GooglePlugin::_syncWorker, this);

            std::cout << "Google plugin loaded" << std::endl;
        }

@@ -76,11 +94,30 @@ namespace blogi {
                        << " WHERE author_name='" << author << "';";
                    Args->database[tid]->exec(sql, res);
                    saved = true;
                } else if(cur.key == "google_notify_email") {
                    _notifyEmail = cur.value;
                    setConfig(*Args->database[tid], "GOOGLE_REVIEW_NOTIFY_EMAIL", cur.value);
                    saved = true;
                } else if(cur.key == "google_sync_days") {
                    int days = atoi(cur.value.c_str());
                    if(days > 0) {
                        _syncDays = days;
                        setConfig(*Args->database[tid], "GOOGLE_REVIEW_SYNC_DAYS", cur.value);
                    }
                    saved = true;
                } else if(cur.key == "google_api_key") {
                    _apiKey = cur.value;
                    setConfig(*Args->database[tid], "GOOGLE_PLACES_API_KEY", cur.value);
                    saved = true;
                } else if(cur.key == "google_place_id") {
                    _placeId = cur.value;
                    setConfig(*Args->database[tid], "GOOGLE_PLACE_ID", cur.value);
                    saved = true;
                }
            }

            json_object *jroot = json_object_new_object();
            json_object_object_add(jroot, "title", json_object_new_string("Google Reviews Approval"));
            json_object_object_add(jroot, "title", json_object_new_string("Google Reviews"));

            if(saved) {
                json_object_object_add(jroot, "message", json_object_new_string("Saved!"));
@@ -93,10 +130,23 @@ namespace blogi {

            json_object *jfields = json_object_new_array();

            auto addTextField = [&](const char *name, const char *label, const std::string &value, const char *type = "text"){
                json_object *jf = json_object_new_object();
                json_object_object_add(jf, "name", json_object_new_string(name));
                json_object_object_add(jf, "label", json_object_new_string(label));
                json_object_object_add(jf, "type", json_object_new_string(type));
                json_object_object_add(jf, "value", json_object_new_string(value.c_str()));
                json_object_array_add(jfields, jf);
            };

            addTextField("google_api_key",       "Google Places API Key",  _apiKey);
            addTextField("google_place_id",       "Google Place ID",        _placeId);
            addTextField("google_notify_email",   "Notification E-Mail",    _notifyEmail);
            addTextField("google_sync_days",      "Sync Interval (days)",   std::to_string(_syncDays), "number");

            dbpp::SQL sql;
            dbpp::DBResult res;
            sql << "SELECT author_name, rating, text, approved FROM google_reviews;";

            int ncount = Args->database[tid]->exec(sql, res);

            for(int i = 0; i < ncount; ++i) {
@@ -119,7 +169,6 @@ namespace blogi {

            json_object_object_add(jform, "fields", jfields);

            // Generate submit button
            json_object *jbuttons = json_object_new_array();
            json_object *jbtn = json_object_new_object();
            json_object_object_add(jbtn, "type", json_object_new_string("submit"));
@@ -162,9 +211,6 @@ namespace blogi {
            }

            if(req.getRequestURL().compare(0, pushUrl.length(), pushUrl) == 0 && req.getRequestType() == POSTREQUEST) {
                // Receive incoming reviews (JSON format)
                size_t datalen = req.getRequestLength();

                if(req.RecvData.size() < req.getRequestLength()){
                    libhttppp::HTTPException exp;
                    exp << "Google invalied PostData size !";
@@ -187,24 +233,8 @@ namespace blogi {
                        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(!author.empty()) {
                            dbpp::SQL sqlexist, sql;
                            dbpp::DBResult res;
                            sqlexist << "SELECT author_name FROM google_reviews WHERE author_name='" << author << "';";
                            int ncount = Args->database[tid]->exec(sqlexist, res);
                            
                            if (ncount > 0) {
                                // update
                                sql << "UPDATE google_reviews SET rating='" << rating 
                                    << "', text='" << text << "', time='" << time 
                                    << "' WHERE author_name='" << author << "';";
                            } else {
                                // insert
                                sql << "INSERT INTO google_reviews (author_name, rating, text, time, approved) VALUES ('"
                                    << author << "', '" << rating << "', '" << text << "', '" << time << "', false);";
                            }
                            Args->database[tid]->exec(sql, res);
                        }
                        if(!author.empty())
                            _processReview(tid, author, rating, text, time);
                    }
                }

@@ -220,6 +250,121 @@ namespace blogi {

            return false;
        }

    private:
        std::thread             _syncThread;
        std::atomic<bool>       _stop{false};
        std::mutex              _mutex;
        std::condition_variable _cv;

        std::string _apiKey;
        std::string _placeId;
        std::string _notifyEmail;
        int         _syncDays = 1;

        void _syncWorker() {
            while (true) {
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    _cv.wait_for(lock, std::chrono::hours(24 * _syncDays),
                                 [this]{ return _stop.load(); });
                }
                if (_stop.load()) break;

                if (!_apiKey.empty() && !_placeId.empty())
                    _fetchFromGoogle();
            }
        }

        void _fetchFromGoogle() {
            try {
                std::string path = "/maps/api/place/details/json?place_id=" + _placeId
                                 + "&fields=reviews&key=" + _apiKey;
                libhttppp::HttpUrl apiUrl("https://maps.googleapis.com" + path);
                libhttppp::HttpClient client(apiUrl);
                client.setTimeout(15);

                libhttppp::HttpRequest req;
                req.setRequestType(GETREQUEST);
                req.setRequestURL(path);

                auto bodyVec = client.Get(req, 3);
                std::string body(bodyVec.begin(), bodyVec.end());

                json_tokener *tok = json_tokener_new();
                json_object *jobj = json_tokener_parse_ex(tok, body.c_str(), (int)body.size());
                json_tokener_free(tok);
                if (!jobj) return;

                json_object *jresult;
                if (json_object_object_get_ex(jobj, "result", &jresult)) {
                    json_object *jreviews;
                    if (json_object_object_get_ex(jresult, "reviews", &jreviews)
                        && json_object_is_type(jreviews, json_type_array)) {
                        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;
                            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))
                                timeStr = std::to_string(json_object_get_int64(jtime));

                            if (!author.empty())
                                _processReview(0, author, rating, text, timeStr);
                        }
                    }
                }
                json_object_put(jobj);
            } catch (const std::exception &e) {
                std::cerr << "Google sync error: " << e.what() << std::endl;
            } catch (...) {
                std::cerr << "Google sync: unknown error" << std::endl;
            }
        }

        void _processReview(int tid, const std::string &author, int rating,
                            const std::string &text, const std::string &time) {
            std::vector<char> buf;
            dbpp::SQL sqlexist;
            dbpp::DBResult res;
            sqlexist << "SELECT author_name FROM google_reviews WHERE author_name='"
                     << dbpp::SQL::escaped(buf, author.c_str()) << "';";
            int ncount = Args->database[tid]->exec(sqlexist, res);

            bool isNew = (ncount == 0);
            buf.clear();

            dbpp::SQL sql;
            if (!isNew) {
                sql << "UPDATE google_reviews SET rating=" << rating
                    << ", text='" << dbpp::SQL::escaped(buf, text.c_str()) << "'"
                    << ", time='" << dbpp::SQL::escaped(buf, time.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 ('"
                    << dbpp::SQL::escaped(buf, author.c_str()) << "', " << rating << ", '"
                    << dbpp::SQL::escaped(buf, text.c_str()) << "', '"
                    << dbpp::SQL::escaped(buf, time.c_str()) << "', false);";
            }
            Args->database[tid]->exec(sql, res);

            if (isNew && !_notifyEmail.empty() && Args->smtp) {
                std::string subject = "New Google Review from " + author;
                std::string body = "A new Google review has been received:\n\n"
                                 "Author: " + author + "\n"
                                 "Rating: " + std::to_string(rating) + " stars\n"
                                 "Review:\n" + text + "\n";
                sendMail(Args->smtp.get(), _notifyEmail.c_str(), subject.c_str(), body);
            }
        }
    };
}