From 68e40d8b80f6370f0b116e18ae2b1ce8dcfeb7b1 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Tue, 3 Jan 2017 21:05:48 +0000 Subject: Implement sending emails of news ebuilds and signup verification --- gentoobrowse-api/api/maintenance.ice | 5 ++ gentoobrowse-api/domain/news-models.ice | 26 +++++++++++ gentoobrowse-api/service/maintenanceCommon.cpp | 54 ++++++++++++++++++++-- gentoobrowse-api/service/maintenanceimpl.h | 2 + .../service/notifications/notifications.ice | 8 +--- .../sql/maintenance/getNotificationEbuildLists.sql | 11 +++++ gentoobrowse-api/service/usersimpl.cpp | 18 +++++++- gentoobrowse-api/unittests/Jamfile.jam | 9 ++++ .../unittests/testNotificationsTriggers.cpp | 50 ++++++++++++++++++++ gentoobrowse-api/unittests/testUsers.cpp | 6 +++ 10 files changed, 177 insertions(+), 12 deletions(-) create mode 100644 gentoobrowse-api/domain/news-models.ice create mode 100644 gentoobrowse-api/service/sql/maintenance/getNotificationEbuildLists.sql create mode 100644 gentoobrowse-api/unittests/testNotificationsTriggers.cpp diff --git a/gentoobrowse-api/api/maintenance.ice b/gentoobrowse-api/api/maintenance.ice index 013acb3..736b913 100644 --- a/gentoobrowse-api/api/maintenance.ice +++ b/gentoobrowse-api/api/maintenance.ice @@ -1,3 +1,5 @@ +#include + module Gentoo { ["cpp:ice_print"] exception GitError { @@ -5,10 +7,13 @@ module Gentoo { int errorClass; string message; }; + interface Maintenance { idempotent void refreshPackageTree(); idempotent void refreshBugs(); idempotent void refreshChangeLogs() throws GitError; + UserNews getUserNews(); + void sendNotifications(); }; }; diff --git a/gentoobrowse-api/domain/news-models.ice b/gentoobrowse-api/domain/news-models.ice new file mode 100644 index 0000000..0b7021f --- /dev/null +++ b/gentoobrowse-api/domain/news-models.ice @@ -0,0 +1,26 @@ +#ifndef GENTOO_NEWS_MODELS +#define GENTOO_NEWS_MODELS + +#include + +module Gentoo { + struct NewsContentTrigger { + int userid; + IntList ebuildids; + IntList packageids; + IntList categoryids; + }; + + sequence NewsContentTriggers; + + struct NewsContent { + Categories categories; + Packages packages; + Ebuilds ebuilds; + }; + + dictionary UserNews; +}; + +#endif + diff --git a/gentoobrowse-api/service/maintenanceCommon.cpp b/gentoobrowse-api/service/maintenanceCommon.cpp index 49101ee..8a55087 100644 --- a/gentoobrowse-api/service/maintenanceCommon.cpp +++ b/gentoobrowse-api/service/maintenanceCommon.cpp @@ -1,9 +1,11 @@ #include "maintenanceimpl.h" #include #include -#include -#include "utils/dbUtils.h" -#include "sql/maintenance/bugAssociate.sql.h" +#include +#include +#include +#include +#include "sql/maintenance/getNotificationEbuildLists.sql.h" namespace Gentoo { namespace Service { @@ -12,6 +14,52 @@ namespace Gentoo { { return c.adapter->getCommunicator()->getProperties(); } + + Gentoo::UserNews + Maintenance::getUserNews(const Ice::Current & current) + { + auto portage = PortagePrx::checkedCast(current.adapter->getCommunicator()->stringToProxy("portage")); + portage->ice_ping(); + auto categories = portage->getAllCategories(); + + UserNews un; + auto t = fetch(sql::maintenance::getNotificationEbuildLists); + std::transform(t.begin(), t.end(), std::inserter(un, un.end()), [&categories,&portage](const NewsContentTriggers::value_type & t) { + NewsContent nc; + std::copy_if(categories.begin(), categories.end(), std::back_inserter(nc.categories), [&t](const Gentoo::CategoryPtr & c) { + return AdHoc::containerContains(t.categoryids, c->categoryid); + }); + for (auto pid : t.packageids) { + auto par = portage->begin_getPackage(pid); + auto vsar = portage->begin_getPackageVersions(pid); + nc.packages.push_back(portage->end_getPackage(par)); + auto vs = portage->end_getPackageVersions(vsar); + std::copy_if(vs.begin(), vs.end(), std::back_inserter(nc.ebuilds), [&t](const Gentoo::EbuildPtr & v) { + return AdHoc::containerContains(t.ebuildids, v->ebuildid); + }); + } + return UserNews::value_type(t.userid, nc); + }); + return un; + } + + void + Maintenance::sendNotifications(const Ice::Current & current) + { + auto notifications = NotificationsPrx::checkedCast(current.adapter->getCommunicator()->stringToProxy("notifications")); + notifications->ice_ping(); + auto mailServer = MailServerPrx::checkedCast(current.adapter->getCommunicator()->stringToProxy("mailserver")); + mailServer->ice_ping(); + auto users = UsersPrx::checkedCast(current.adapter->getCommunicator()->stringToProxy("users")); + users->ice_ping(); + + for (const auto & trigger : getUserNews(current)) { + auto user = users->get(trigger.first); + auto email = notifications->getNews(user, trigger.second); + mailServer->sendEmail(email); + users->mailshotsent(user->userid); + } + } } } diff --git a/gentoobrowse-api/service/maintenanceimpl.h b/gentoobrowse-api/service/maintenanceimpl.h index c150043..dc6cbbf 100644 --- a/gentoobrowse-api/service/maintenanceimpl.h +++ b/gentoobrowse-api/service/maintenanceimpl.h @@ -38,6 +38,8 @@ namespace Gentoo { void refreshPackageTree(const Ice::Current &) override; void refreshBugs(const Ice::Current &) override; void refreshChangeLogs(const Ice::Current &) override; + Gentoo::UserNews getUserNews(const Ice::Current &) override; + void sendNotifications(const Ice::Current &) override; private: void updateFileTypes(DB::Connection *) const; diff --git a/gentoobrowse-api/service/notifications/notifications.ice b/gentoobrowse-api/service/notifications/notifications.ice index a60fa28..4a2673f 100644 --- a/gentoobrowse-api/service/notifications/notifications.ice +++ b/gentoobrowse-api/service/notifications/notifications.ice @@ -2,7 +2,7 @@ #define GENTOO_NOTIFICATIONS #include -#include +#include module Gentoo { class MimePart { @@ -29,12 +29,6 @@ module Gentoo { string message; }; - struct NewsContent { - Categories categories; - Packages packages; - Ebuilds ebuilds; - }; - interface Notifications { Email getSignup(NewUser user); Email getNews(User user, NewsContent nc); diff --git a/gentoobrowse-api/service/sql/maintenance/getNotificationEbuildLists.sql b/gentoobrowse-api/service/sql/maintenance/getNotificationEbuildLists.sql new file mode 100644 index 0000000..35453fc --- /dev/null +++ b/gentoobrowse-api/service/sql/maintenance/getNotificationEbuildLists.sql @@ -0,0 +1,11 @@ +SELECT u.userid, + array_agg(DISTINCT e.ebuildid) ebuildids, + array_agg(DISTINCT p.packageid) packageids, + array_agg(DISTINCT p.categoryid) categoryids +FROM gentoobrowse.users u, gentoobrowse.user_packages up, gentoobrowse.ebuilds e, gentoobrowse.packages p +WHERE u.userid = up.userid +AND p.packageid = e.packageid +AND up.trackedsince < e.firstseen +AND up.packageid = e.packageid +AND coalesce(u.lastmailshot, u.signedup) < e.firstseen +GROUP BY u.userid diff --git a/gentoobrowse-api/service/usersimpl.cpp b/gentoobrowse-api/service/usersimpl.cpp index c2f45a7..60d0b64 100644 --- a/gentoobrowse-api/service/usersimpl.cpp +++ b/gentoobrowse-api/service/usersimpl.cpp @@ -1,4 +1,6 @@ #include "usersimpl.h" +#include +#include #include #include #include @@ -10,6 +12,7 @@ #include #include #include +#include namespace Gentoo { namespace Service { @@ -43,9 +46,20 @@ Users::find(const std::string & username, const Ice::Current &) } Gentoo::NewUserPtr -Users::create(const std::string & username, const std::string & password, const std::string & realname, const std::string & email, const Ice::Current &) +Users::create(const std::string & username, const std::string & password, const std::string & realname, const std::string & email, const Ice::Current & current) { - return fetch(sql::users::create, username, password, realname, email); + auto notifications = NotificationsPrx::checkedCast(current.adapter->getCommunicator()->stringToProxy("notifications")); + notifications->ice_ping(); + auto mailServer = MailServerPrx::checkedCast(current.adapter->getCommunicator()->stringToProxy("mailserver")); + mailServer->ice_ping(); + + auto dbc = db->get(); + DB::TransactionScope tx(dbc.get()); + auto newUser = fetch(sql::users::create, username, password, realname, email); + auto mail = notifications->getSignup(newUser); + mailServer->sendEmail(mail); + + return newUser; } void diff --git a/gentoobrowse-api/unittests/Jamfile.jam b/gentoobrowse-api/unittests/Jamfile.jam index 60ee5e6..ba030eb 100644 --- a/gentoobrowse-api/unittests/Jamfile.jam +++ b/gentoobrowse-api/unittests/Jamfile.jam @@ -111,6 +111,15 @@ run testCommon : testNotifications ; +run + testNotificationsTriggers.cpp + : : : + ../db/schema.sql + BOOST_TEST_DYN_LINK + testCommon + testCommon + : testNotificationsTriggers ; + run testBugs.cpp : : : diff --git a/gentoobrowse-api/unittests/testNotificationsTriggers.cpp b/gentoobrowse-api/unittests/testNotificationsTriggers.cpp new file mode 100644 index 0000000..6455627 --- /dev/null +++ b/gentoobrowse-api/unittests/testNotificationsTriggers.cpp @@ -0,0 +1,50 @@ +#define BOOST_TEST_MODULE TestNotificationsTriggers +#include + +#include "mockDefs.h" + +BOOST_GLOBAL_FIXTURE( Service ); + +BOOST_FIXTURE_TEST_SUITE(tp, TestClient); + +BOOST_AUTO_TEST_CASE( triggers ) +{ + auto t = m->getUserNews(); + BOOST_REQUIRE_EQUAL(1, t.size()); + BOOST_REQUIRE_EQUAL(1, t.begin()->first); + BOOST_REQUIRE_EQUAL(2, t.begin()->second.categories.size()); + std::sort(t.begin()->second.categories.begin(), t.begin()->second.categories.end(), [](auto a, auto b) { return a->categoryid < b->categoryid; }); + BOOST_REQUIRE_EQUAL(311, t.begin()->second.categories[0]->categoryid); + BOOST_REQUIRE_EQUAL(313, t.begin()->second.categories[1]->categoryid); + BOOST_REQUIRE_EQUAL(2, t.begin()->second.packages.size()); + std::sort(t.begin()->second.packages.begin(), t.begin()->second.packages.end(), [](auto a, auto b) { return a->packageid < b->packageid; }); + BOOST_REQUIRE_EQUAL(49517, t.begin()->second.packages[0]->packageid); + BOOST_REQUIRE_EQUAL(54144, t.begin()->second.packages[1]->packageid); + BOOST_REQUIRE_EQUAL(7, t.begin()->second.ebuilds.size()); + std::sort(t.begin()->second.ebuilds.begin(), t.begin()->second.ebuilds.end(), [](auto a, auto b) { return a->ebuildid < b->ebuildid; }); + BOOST_REQUIRE_EQUAL(657508, t.begin()->second.ebuilds[0]->ebuildid); + BOOST_REQUIRE_EQUAL(657509, t.begin()->second.ebuilds[1]->ebuildid); + BOOST_REQUIRE_EQUAL(658228, t.begin()->second.ebuilds[2]->ebuildid); + BOOST_REQUIRE_EQUAL(658511, t.begin()->second.ebuilds[3]->ebuildid); + BOOST_REQUIRE_EQUAL(659908, t.begin()->second.ebuilds[4]->ebuildid); + BOOST_REQUIRE_EQUAL(660220, t.begin()->second.ebuilds[5]->ebuildid); + BOOST_REQUIRE_EQUAL(680613, t.begin()->second.ebuilds[6]->ebuildid); +} + +BOOST_AUTO_TEST_CASE( send ) +{ + auto ms = getProxy("mailserver"); + BOOST_REQUIRE(ms); + + BOOST_REQUIRE(!u->get(1)->lastmailshot); + m->sendNotifications(); + BOOST_REQUIRE(u->get(1)->lastmailshot); + const auto & es = ms->getSentEmails(); + BOOST_REQUIRE_EQUAL(1, es.size()); + BOOST_REQUIRE_EQUAL("Gentoo Browse: Latest updates", es[0]->subject); + BOOST_REQUIRE_EQUAL("Dan Goodliffe", es[0]->to.name); + BOOST_REQUIRE_EQUAL("Gentoo Browse", es[0]->from.name); +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/gentoobrowse-api/unittests/testUsers.cpp b/gentoobrowse-api/unittests/testUsers.cpp index e515b77..7f9eaee 100644 --- a/gentoobrowse-api/unittests/testUsers.cpp +++ b/gentoobrowse-api/unittests/testUsers.cpp @@ -43,7 +43,13 @@ BOOST_AUTO_TEST_CASE( authenticateVerify ) BOOST_AUTO_TEST_CASE( create ) { + auto ms = getProxy("mailserver"); + BOOST_REQUIRE(ms); auto user = u->create("testuser", "testpass", "Test User", "test@user.com"); + BOOST_REQUIRE_EQUAL(1, ms->getSentEmails().size()); + BOOST_REQUIRE_EQUAL("Gentoo Browse: Welcome", ms->getSentEmails().front()->subject); + BOOST_REQUIRE_EQUAL("test@user.com", ms->getSentEmails().front()->to.address); + BOOST_REQUIRE_EQUAL("Test User", ms->getSentEmails().front()->to.name); BOOST_REQUIRE_EQUAL(3, user->userid); BOOST_REQUIRE_EQUAL("testuser", user->username); BOOST_REQUIRE_EQUAL("Test User", user->userrealname); -- cgit v1.2.3