summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Goodliffe <dan@randomdan.homeip.net>2016-12-02 21:27:50 +0000
committerDan Goodliffe <dan@randomdan.homeip.net>2016-12-02 21:27:50 +0000
commit4861233f34f96182df234c35ba6ca9474d29b74a (patch)
treee561e24bcb81407bc1cc1fd8b729dcfa9858b81c
parentFix use flag ordering to be not locale sensitive and resulting changes in tes... (diff)
downloadgentoobrowse-api-4861233f34f96182df234c35ba6ca9474d29b74a.tar.bz2
gentoobrowse-api-4861233f34f96182df234c35ba6ca9474d29b74a.tar.xz
gentoobrowse-api-4861233f34f96182df234c35ba6ca9474d29b74a.zip
Basic notification service implementation
-rw-r--r--gentoobrowse-api/service/Jamfile.jam7
-rw-r--r--gentoobrowse-api/service/main.cpp4
-rw-r--r--gentoobrowse-api/service/notifications/Jamfile.jam44
-rw-r--r--gentoobrowse-api/service/notifications/mailserverimpl.cpp64
-rw-r--r--gentoobrowse-api/service/notifications/mailserverimpl.h20
-rw-r--r--gentoobrowse-api/service/notifications/notifications.ice49
-rw-r--r--gentoobrowse-api/service/notifications/notificationsimpl.cpp59
-rw-r--r--gentoobrowse-api/service/notifications/notificationsimpl.h29
-rw-r--r--gentoobrowse-api/service/notifications/xslt/base.xslt61
-rw-r--r--gentoobrowse-api/service/notifications/xslt/news.xslt57
-rw-r--r--gentoobrowse-api/service/notifications/xslt/signup.xslt24
-rw-r--r--gentoobrowse-api/service/notifications/xsltStreamSerializer.cpp82
-rw-r--r--gentoobrowse-api/service/notifications/xsltStreamSerializer.h28
-rw-r--r--gentoobrowse-api/unittests/Jamfile.jam8
-rw-r--r--gentoobrowse-api/unittests/mockDefs.cpp5
-rw-r--r--gentoobrowse-api/unittests/mockDefs.h3
-rw-r--r--gentoobrowse-api/unittests/testNotifications.cpp102
17 files changed, 645 insertions, 1 deletions
diff --git a/gentoobrowse-api/service/Jamfile.jam b/gentoobrowse-api/service/Jamfile.jam
index a065393..7300b68 100644
--- a/gentoobrowse-api/service/Jamfile.jam
+++ b/gentoobrowse-api/service/Jamfile.jam
@@ -4,10 +4,11 @@ lib icetray : : : : <include>/usr/include/icetray ;
lib git2 ;
lib gentoobrowse-service :
- [ glob-tree *.cpp : bin ]
+ [ glob-tree *.cpp : bin notifications ]
[ glob-tree *.sql ]
:
<library>..//adhocutil
+ <library>notifications
<library>icetray
<library>git2
<library>..//dbppcore
@@ -15,6 +16,8 @@ lib gentoobrowse-service :
<library>..//slicer
<library>..//slicer-db
<library>../api//gentoobrowse-api
+ <library>notifications//gentoobrowse-service-notifications
+ <implicit-dependency>notifications//gentoobrowse-service-notifications
<implicit-dependency>../api//gentoobrowse-api
<library>..//boost_system
<library>..//boost_thread
@@ -27,6 +30,8 @@ lib gentoobrowse-service :
: :
<include>.
<implicit-dependency>../api//gentoobrowse-api
+ <library>notifications//gentoobrowse-service-notifications
+ <implicit-dependency>notifications//gentoobrowse-service-notifications
<library>../api//gentoobrowse-api
;
diff --git a/gentoobrowse-api/service/main.cpp b/gentoobrowse-api/service/main.cpp
index bdf6ed5..b7b18a2 100644
--- a/gentoobrowse-api/service/main.cpp
+++ b/gentoobrowse-api/service/main.cpp
@@ -4,6 +4,8 @@
#include "portageimpl.h"
#include "usersimpl.h"
#include "maintenanceimpl.h"
+#include "notificationsimpl.h"
+#include "mailserverimpl.h"
namespace Gentoo {
namespace Service {
@@ -15,6 +17,8 @@ class Api : public IceTray::Service {
adp->add(new Portage(db), ic->stringToIdentity("portage"));
adp->add(new Maintenance(db), ic->stringToIdentity("maintenance"));
adp->add(new Users(db), ic->stringToIdentity("users"));
+ adp->add(new Notifications(), ic->stringToIdentity("notifications"));
+ adp->add(new MailServer(), ic->stringToIdentity("mailserver"));
}
};
diff --git a/gentoobrowse-api/service/notifications/Jamfile.jam b/gentoobrowse-api/service/notifications/Jamfile.jam
new file mode 100644
index 0000000..a168649
--- /dev/null
+++ b/gentoobrowse-api/service/notifications/Jamfile.jam
@@ -0,0 +1,44 @@
+import type : register ;
+import generators : register-standard ;
+
+type.register XSLT : xslt ;
+
+generators.register-standard xslt.h : XSLT : H ;
+
+h base-xslt : xslt/base.xslt : <slicer>yes ;
+h signup-xslt : xslt/signup.xslt : <slicer>yes ;
+h news-xslt : xslt/news.xslt : <slicer>yes ;
+
+lib exslt ;
+lib xslt : : : : <include>/usr/include/libxslt ;
+lib xml2 : : : : <include>/usr/include/libxml2 ;
+lib esmtp ;
+lib slicer-xml ;
+lib boost_system ;
+
+lib gentoobrowse-service-notifications :
+ [ glob *.ice *.cpp ]
+ :
+ <slicer>yes
+ <library>xslt
+ <library>xml2
+ <library>esmtp
+ <library>../../..//libxmlpp
+ <library>boost_system
+ <library>slicer-xml
+ <library>../../domain//gentoobrowse-domain
+ <implicit-dependency>../../domain//gentoobrowse-domain
+ <dependency>base-xslt
+ <dependency>signup-xslt
+ <dependency>news-xslt
+ : :
+ <include>.
+ ;
+
+actions xslt.h
+{
+ ( cd $(2:D) ; xxd -i $(2:B)$(2:S) ) > $(1)
+}
+
+IMPORT $(__name__) : xslt.h : : xslt.h ;
+
diff --git a/gentoobrowse-api/service/notifications/mailserverimpl.cpp b/gentoobrowse-api/service/notifications/mailserverimpl.cpp
new file mode 100644
index 0000000..c3dfdfa
--- /dev/null
+++ b/gentoobrowse-api/service/notifications/mailserverimpl.cpp
@@ -0,0 +1,64 @@
+#include "mailserverimpl.h"
+#include <libesmtp.h>
+#include <Ice/ObjectAdapter.h>
+#include <Ice/Communicator.h>
+
+namespace Gentoo {
+ namespace Service {
+ typedef std::vector<std::string> Parts;
+ typedef std::pair<Parts::iterator, Parts::const_iterator> PartsProgress;
+
+ void
+ MailServer::sendEmail(const Gentoo::EmailPtr & msg, const Ice::Current & c)
+ {
+ auto props = c.adapter->getCommunicator()->getProperties();
+ smtp_session_t session = smtp_create_session();
+ smtp_message_t message = smtp_add_message(session);
+ auto server = props->getPropertyWithDefault("GentooBrowseAPI.MailServer", "localhost");
+ smtp_set_server(session, server.c_str());
+ smtp_set_header(message, "To", msg->to.name.c_str(), msg->to.address.c_str());
+ smtp_set_header(message, "From", msg->from.name.c_str(), msg->from.address.c_str());
+ smtp_set_header(message, "Subject", msg->subject.c_str());
+ Parts parts;
+ parts.emplace_back("Content-Type: multipart/alternative; boundary=\"<<divider>>\"\r\n");
+ parts.emplace_back("MIME-Version: 1.0\r\n");
+ parts.emplace_back("Content-Transfer-Encoding: binary\r\n");
+ smtp_add_recipient(message, msg->to.address.c_str());
+ for (const auto & p : msg->body) {
+ parts.emplace_back("\r\n--<<divider>>\r\nContent-Type: " + p->mimetype + "; utf-8\r\n\r\n");
+ parts.emplace_back(p->payload);
+ }
+ parts.emplace_back("\r\n--<<divider>>--\r\n");
+ PartsProgress pp { parts.begin(), parts.end() };
+ smtp_set_messagecb(message, writeBody, &pp);
+ if (!smtp_start_session(session)) {
+ char buf[BUFSIZ];
+ auto b = smtp_strerror(smtp_errno(), buf, sizeof(buf));
+ assert(b);
+ SendEmailFailed e(b);
+ smtp_destroy_session(session);
+ throw e;
+ }
+ smtp_destroy_session(session);
+ }
+
+ const char *
+ MailServer::writeBody(void **, int * len, void * arg)
+ {
+ auto parts = static_cast<PartsProgress *>(arg);
+ if (len == NULL || parts->first == parts->second) {
+ return NULL;
+ }
+ const auto & p = *parts->first++;
+ *len = p.length();
+ return p.data();
+ }
+ }
+
+ void
+ SendEmailFailed::ice_print(std::ostream & buf) const
+ {
+ buf << "Failed to send email: " << message;
+ }
+}
+
diff --git a/gentoobrowse-api/service/notifications/mailserverimpl.h b/gentoobrowse-api/service/notifications/mailserverimpl.h
new file mode 100644
index 0000000..23b17c2
--- /dev/null
+++ b/gentoobrowse-api/service/notifications/mailserverimpl.h
@@ -0,0 +1,20 @@
+#ifndef MAILSERVERIMPL_H
+#define MAILSERVERIMPL_H
+
+#include <notifications.h>
+#include <visibility.h>
+
+namespace Gentoo {
+ namespace Service {
+ class DLL_PUBLIC MailServer : public Gentoo::MailServer {
+ public:
+ void sendEmail(const Gentoo::EmailPtr & msg, const Ice::Current &) override;
+
+ private:
+ static const char * writeBody(void ** buf, int * len, void * arg);
+ };
+ }
+}
+
+#endif
+
diff --git a/gentoobrowse-api/service/notifications/notifications.ice b/gentoobrowse-api/service/notifications/notifications.ice
new file mode 100644
index 0000000..a60fa28
--- /dev/null
+++ b/gentoobrowse-api/service/notifications/notifications.ice
@@ -0,0 +1,49 @@
+#ifndef GENTOO_NOTIFICATIONS
+#define GENTOO_NOTIFICATIONS
+
+#include <user-models.ice>
+#include <portage-models.ice>
+
+module Gentoo {
+ class MimePart {
+ string mimetype;
+ string payload;
+ };
+
+ sequence<MimePart> MimeParts;
+
+ struct Address {
+ string name;
+ string address;
+ };
+
+ class Email {
+ Address to;
+ Address from;
+ string subject;
+ MimeParts body;
+ };
+
+ ["cpp:ice_print"]
+ exception SendEmailFailed {
+ string message;
+ };
+
+ struct NewsContent {
+ Categories categories;
+ Packages packages;
+ Ebuilds ebuilds;
+ };
+
+ interface Notifications {
+ Email getSignup(NewUser user);
+ Email getNews(User user, NewsContent nc);
+ };
+
+ interface MailServer {
+ idempotent void sendEmail(Email msg) throws SendEmailFailed;
+ };
+};
+
+#endif
+
diff --git a/gentoobrowse-api/service/notifications/notificationsimpl.cpp b/gentoobrowse-api/service/notifications/notificationsimpl.cpp
new file mode 100644
index 0000000..d7dbee4
--- /dev/null
+++ b/gentoobrowse-api/service/notifications/notificationsimpl.cpp
@@ -0,0 +1,59 @@
+#include "notificationsimpl.h"
+#include <libxml/parser.h>
+#include <base-xslt.h>
+#include <news-xslt.h>
+#include <signup-xslt.h>
+#include <string.h>
+#include <slicer/slicer.h>
+#include "xsltStreamSerializer.h"
+
+namespace Gentoo {
+ namespace Service {
+ xmlDocPtr xsltDocLoaderFunc (const xmlChar * URI, xmlDictPtr, int, void *, xsltLoadType)
+ {
+#define MATCH(name) \
+ if (xmlStrcmp(URI, BAD_CAST #name ".xslt") == 0) { \
+ return xmlParseMemory((char *)name ## _xslt, name ## _xslt_len); \
+ }
+ MATCH(base)
+ MATCH(news)
+ MATCH(signup)
+ return NULL;
+#undef MATCH
+ }
+
+ Notifications::Notifications()
+ {
+ xsltSetLoaderFunc(&xsltDocLoaderFunc);
+ news = xsltSSPtr(xsltParseStylesheetFile(BAD_CAST "news.xslt"), xsltFreeStylesheet);
+ signup = xsltSSPtr(xsltParseStylesheetFile(BAD_CAST "signup.xslt"), xsltFreeStylesheet);
+ xsltSetLoaderFunc(NULL);
+ }
+
+ Gentoo::EmailPtr Notifications::getSignup(const Gentoo::NewUserPtr & u, const Ice::Current &)
+ {
+ auto e = basicMail("Welcome", u);
+ Slicer::SerializeAny<XsltStreamSerializer>(u, e, signup.get());
+ return e;
+ }
+
+ Gentoo::EmailPtr Notifications::getNews(const Gentoo::UserPtr & u, const Gentoo::NewsContent & c, const Ice::Current &)
+ {
+ auto e = basicMail("Latest updates", u);
+ Slicer::SerializeAny<XsltStreamSerializer>(c, e, news.get());
+ return e;
+ }
+
+ Gentoo::EmailPtr Notifications::basicMail(const std::string & subject, Gentoo::UserPtr u)
+ {
+ Gentoo::EmailPtr e = new Gentoo::Email();
+ e->subject = "Gentoo Browse: " + subject;
+ e->from.name = "Gentoo Browse";
+ e->from.address = "noreply@gentoobrowse.randomdan.homeip.net";
+ e->to.name = u->userrealname;
+ e->to.address = u->useremail;
+ return e;
+ }
+ }
+}
+
diff --git a/gentoobrowse-api/service/notifications/notificationsimpl.h b/gentoobrowse-api/service/notifications/notificationsimpl.h
new file mode 100644
index 0000000..1d3b1fc
--- /dev/null
+++ b/gentoobrowse-api/service/notifications/notificationsimpl.h
@@ -0,0 +1,29 @@
+#ifndef NOTIFICATIONSIMPL_H
+#define NOTIFICATIONSIMPL_H
+
+#include <notifications.h>
+#include <visibility.h>
+#include <memory>
+#include <libxslt/documents.h>
+
+namespace Gentoo {
+ namespace Service {
+ class DLL_PUBLIC Notifications : public Gentoo::Notifications {
+ public:
+ Notifications();
+
+ Gentoo::EmailPtr getSignup(const Gentoo::NewUserPtr &, const Ice::Current &) override;
+ Gentoo::EmailPtr getNews(const Gentoo::UserPtr &, const Gentoo::NewsContent &, const Ice::Current &) override;
+
+ private:
+ Gentoo::EmailPtr basicMail(const std::string &, Gentoo::UserPtr u);
+
+ typedef std::shared_ptr<xsltStylesheet> xsltSSPtr;
+ xsltSSPtr news;
+ xsltSSPtr signup;
+ };
+ }
+}
+
+#endif
+
diff --git a/gentoobrowse-api/service/notifications/xslt/base.xslt b/gentoobrowse-api/service/notifications/xslt/base.xslt
new file mode 100644
index 0000000..9db7752
--- /dev/null
+++ b/gentoobrowse-api/service/notifications/xslt/base.xslt
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:project2="http://project2.randomdan.homeip.net" exclude-result-prefixes="project2" >
+ <xsl:output encoding="utf-8" method="html" media-type="text/html" indent="yes" />
+
+ <xsl:template match="/*">
+ <xsl:text disable-output-escaping="yes">&lt;!DOCTYPE html&gt;&#10;</xsl:text>
+ <html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <link href="http://gentoobrowse.randomdan.homeip.net/css/bootstrap.min.css" rel="stylesheet" media="screen" />
+ <link href="http://gentoobrowse.randomdan.homeip.net/css/bootstrap-responsive.min.css" rel="stylesheet" media="screen" />
+ <link href="http://gentoobrowse.randomdan.homeip.net/css/general.css" rel="stylesheet" media="screen" />
+ <title><xsl:call-template name="title" /></title>
+ </head>
+ <body data-spy="scroll" data-target=".bs-docs-sidebar">
+ <!-- nav -->
+ <nav class="navbar navbar-inverse navbar-fixed-top">
+ <div class="navbar-inner">
+ <div class="container">
+ <a class="brand" href="http://gentoobrowse.randomdan.homeip.net/">Gentoo Browse</a>
+ </div>
+ </div>
+ </nav>
+
+ <!-- page content -->
+ <header class="jumbotron subhead">
+ <div class="container">
+ <h1>
+ <xsl:call-template name="title" />
+ </h1>
+ </div>
+ </header>
+ <div class="container">
+ <article class="row">
+ <xsl:call-template name="content" />
+ <section class="signoff">
+ </section>
+ </article>
+ </div>
+
+ <!-- footer -->
+ <footer class="footer">
+ <div class="container">
+ <p>Gentoo Browse is not an official Gentoo website. The name "Gentoo" and the "g" logo are trademarks of the Gentoo Foundation, Inc.</p>
+ </div>
+ </footer>
+
+ <script src="http://code.jquery.com/jquery-latest.js"></script>
+ <script src="http://gentoobrowse.randomdan.homeip.net/js/bootstrap.min.js"></script>
+ <script src="http://gentoobrowse.randomdan.homeip.net/js/js.js"></script>
+ </body>
+ </html>
+ </xsl:template>
+
+ <xsl:template name="head">
+ </xsl:template>
+
+ <xsl:template name="header">
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/gentoobrowse-api/service/notifications/xslt/news.xslt b/gentoobrowse-api/service/notifications/xslt/news.xslt
new file mode 100644
index 0000000..6b2ea29
--- /dev/null
+++ b/gentoobrowse-api/service/notifications/xslt/news.xslt
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://exslt.org/strings" xmlns:project2="http://project2.randomdan.homeip.net" version="1.0" exclude-result-prefixes="project2 fn">
+ <xsl:import href="base.xslt"/>
+ <xsl:output encoding="utf-8" method="html" media-type="text/html" indent="yes"/>
+
+ <xsl:template name="title">Latest news</xsl:template>
+
+ <xsl:template name="content">
+ <xsl:variable name="packages" select="packages/package[packageid = ../../ebuilds/ebuild/packageid]"/>
+ <xsl:variable name="categories" select="categories/category[categoryid = $packages/categoryid]"/>
+ <section>
+ <h2 class="page-header">New versions of tracked packages</h2>
+ <div class="span3 bs-docs-sidebar">
+ <ul class="nav nav-list bs-docs-sidenav">
+ <xsl:for-each select="$categories">
+ <xsl:sort select="name" data-type="text" order="ascending"/>
+ <li>
+ <a>
+ <xsl:attribute name="href">http://gentoobrowse.randomdan.homeip.net/packages/<xsl:value-of select="name" /></xsl:attribute>
+ <i class="icon-chevron-right"></i>
+ <xsl:value-of select="name" />
+ </a>
+ </li>
+ </xsl:for-each>
+ </ul>
+ </div>
+ <div class="span9">
+ <xsl:for-each select="$categories">
+ <xsl:sort select="name" data-type="text" order="ascending"/>
+ <xsl:variable name="category" select="." />
+ <xsl:for-each select="$packages[categoryid = current()/categoryid]">
+ <xsl:sort select="name" data-type="text" order="ascending"/>
+ <xsl:variable name="package" select="." />
+ <p>
+ <a>
+ <xsl:attribute name="href">http://gentoobrowse.randomdan.homeip.net/packages/<xsl:value-of select="$category/name" /></xsl:attribute>
+ <xsl:value-of select="$category/name" />
+ </a>
+ /
+ <a>
+ <xsl:attribute name="href">http://gentoobrowse.randomdan.homeip.net/packages/<xsl:value-of select="$category/name" />/<xsl:value-of select="name" /></xsl:attribute>
+ <xsl:value-of select="name" />
+ </a>
+ :
+ <xsl:value-of select="description" />
+ </p>
+ <xsl:for-each select="../../ebuilds/ebuild[packageid = current()/packageid]">
+ <p>
+ v<xsl:value-of select="version" />
+ </p>
+ </xsl:for-each>
+ </xsl:for-each>
+ </xsl:for-each>
+ </div>
+ </section>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/gentoobrowse-api/service/notifications/xslt/signup.xslt b/gentoobrowse-api/service/notifications/xslt/signup.xslt
new file mode 100644
index 0000000..e579835
--- /dev/null
+++ b/gentoobrowse-api/service/notifications/xslt/signup.xslt
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:import href="base.xslt"/>
+
+ <xsl:template name="title">Welcome to Gentoo Browse</xsl:template>
+
+ <xsl:template name="content">
+ <section>
+ <h2 class="page-header">Confirmation</h2>
+ <p>
+ Click <a>
+ <xsl:attribute name="href">
+ <xsl:text>http://gentoobrowse.randomdan.homeip.net/user/verify/</xsl:text>
+ <xsl:value-of select="verifyguid" />
+ </xsl:attribute>
+ <xsl:text>http://gentoobrowse.randomdan.homeip.net/user/verify/</xsl:text>
+ <xsl:value-of select="verifyguid" />
+ </a> to confirm your account.
+ </p>
+ <p>Or alternatively, copy and paste this code into the confirmation page: <xsl:value-of select="verifyguid" /></p>
+ </section>
+ </xsl:template>
+</xsl:stylesheet>
+
diff --git a/gentoobrowse-api/service/notifications/xsltStreamSerializer.cpp b/gentoobrowse-api/service/notifications/xsltStreamSerializer.cpp
new file mode 100644
index 0000000..5a71b35
--- /dev/null
+++ b/gentoobrowse-api/service/notifications/xsltStreamSerializer.cpp
@@ -0,0 +1,82 @@
+#include "xsltStreamSerializer.h"
+#include <libxslt/xsltInternals.h>
+#include <libxml/HTMLtree.h>
+#include <factory.impl.h>
+#include <processPipes.h>
+#include <sys/wait.h>
+
+namespace Gentoo {
+ static int xmlstrmclosecallback(void * context)
+ {
+ ((std::ostream*)context)->flush();
+ return 0;
+ }
+
+ static int xmlstrmwritecallback(void * context, const char * buffer, int len)
+ {
+ ((std::ostream*)context)->write(buffer, len);
+ return len;
+ }
+
+ XsltStreamSerializer::XsltStreamSerializer(Gentoo::EmailPtr e, xsltStylesheet * ss) :
+ Slicer::XmlDocumentSerializer(doc),
+ mail(e),
+ doc(nullptr),
+ stylesheet(ss)
+ {
+ }
+
+ XsltStreamSerializer::~XsltStreamSerializer()
+ {
+ delete doc;
+ }
+
+ void
+ XsltStreamSerializer::Serialize(Slicer::ModelPartForRootPtr mp)
+ {
+ Slicer::XmlDocumentSerializer::Serialize(mp);
+ auto result = std::shared_ptr<xmlDoc>(xsltApplyStylesheet(stylesheet, doc->cobj(), nullptr), xmlFreeDoc);
+ if (!result) {
+ throw xmlpp::exception("Failed to apply XSL transform");
+ }
+ appendText(result.get());
+ appendHtml(result.get());
+ }
+
+ void XsltStreamSerializer::appendHtml(xmlDoc * result) const
+ {
+ std::stringstream strm;
+ xmlOutputBufferPtr buf = xmlOutputBufferCreateIO(xmlstrmwritecallback, xmlstrmclosecallback, &strm, NULL);
+ htmlDocContentDumpFormatOutput(buf, result, "utf-8", 0);
+ xmlOutputBufferClose(buf);
+ mail->body.push_back(new Gentoo::MimePart("text/html", strm.str()));
+ }
+
+ void XsltStreamSerializer::appendText(xmlDoc * result) const
+ {
+ std::stringstream strm;
+ std::vector<std::string> callLynx;
+ callLynx.push_back("/usr/bin/lynx");
+ callLynx.push_back("-dump");
+ callLynx.push_back("-stdin");
+ std::string widthArg = "-width=105";
+ callLynx.push_back(widthArg);
+ AdHoc::System::ProcessPipes fds(callLynx, true, true, false);
+ FILE * lynxIn = fdopen(fds.fdIn(), "w");
+ // Fixed encoding as we want the result to go back into a ustring
+ htmlNodeDumpFileFormat(lynxIn, result, xmlDocGetRootElement(result), "utf-8", 0);
+ fclose(lynxIn);
+ char buf[1024];
+ int r;
+ while ((r = read(fds.fdOut(), buf, sizeof(buf))) > 0) {
+ strm.write(buf, r);
+ }
+ int status;
+ waitpid(fds.pid(), &status, 0);
+ if (status != 0) {
+ throw std::runtime_error("Lynx failed");
+ }
+ mail->body.push_back(new Gentoo::MimePart("text/plain", strm.str()));
+ }
+}
+
diff --git a/gentoobrowse-api/service/notifications/xsltStreamSerializer.h b/gentoobrowse-api/service/notifications/xsltStreamSerializer.h
new file mode 100644
index 0000000..492b954
--- /dev/null
+++ b/gentoobrowse-api/service/notifications/xsltStreamSerializer.h
@@ -0,0 +1,28 @@
+#ifndef ICESPIDER_CORE_XSLTSTREAMSERIALIZER_H
+#define ICESPIDER_CORE_XSLTSTREAMSERIALIZER_H
+
+#include <slicer/xml/serializer.h>
+#include <visibility.h>
+#include <libxslt/transform.h>
+#include <notifications.h>
+
+namespace Gentoo {
+ class DLL_PUBLIC XsltStreamSerializer : public Slicer::XmlDocumentSerializer {
+ public:
+ XsltStreamSerializer(Gentoo::EmailPtr, xsltStylesheet *);
+ ~XsltStreamSerializer();
+
+ void Serialize(Slicer::ModelPartForRootPtr mp) override;
+
+ protected:
+ void appendHtml(xmlDoc *) const;
+ void appendText(xmlDoc *) const;
+
+ Gentoo::EmailPtr mail;
+ xmlpp::Document * doc;
+ xsltStylesheet * stylesheet;
+ };
+}
+
+#endif
+
diff --git a/gentoobrowse-api/unittests/Jamfile.jam b/gentoobrowse-api/unittests/Jamfile.jam
index 92501c0..70fa90f 100644
--- a/gentoobrowse-api/unittests/Jamfile.jam
+++ b/gentoobrowse-api/unittests/Jamfile.jam
@@ -98,6 +98,14 @@ run
: testUsers ;
run
+ testNotifications.cpp
+ : : :
+ <dependency>../db/schema.sql
+ <define>BOOST_TEST_DYN_LINK
+ <library>testCommon
+ : testNotifications ;
+
+run
testBugs.cpp
: : :
<dependency>../db/schema.sql
diff --git a/gentoobrowse-api/unittests/mockDefs.cpp b/gentoobrowse-api/unittests/mockDefs.cpp
index 70f5b69..eeab5f2 100644
--- a/gentoobrowse-api/unittests/mockDefs.cpp
+++ b/gentoobrowse-api/unittests/mockDefs.cpp
@@ -2,6 +2,9 @@
#include <definedDirs.h>
Service::Service() :
+ IceTray::DryIce({
+ "--GentooBrowseAPI.MailServer=smtp.random.lan:25",
+ }),
PQ::Mock("user=postgres dbname=postgres", "GentooBrowseAPI", {
rootDir.parent_path() / "db" / "schema.sql",
rootDir / "data.sql" })
@@ -22,6 +25,8 @@ Maintenance::Maintenance() :
TestClient::TestClient() :
m(getProxy<Gentoo::MaintenancePrx>("maintenance")),
p(getProxy<Gentoo::PortagePrx>("portage")),
+ n(getProxy<Gentoo::NotificationsPrx>("notifications")),
+ ms(getProxy<Gentoo::MailServerPrx>("mailserver")),
u(getProxy<Gentoo::UsersPrx>("users"))
{
}
diff --git a/gentoobrowse-api/unittests/mockDefs.h b/gentoobrowse-api/unittests/mockDefs.h
index 9a35c1f..a0c044a 100644
--- a/gentoobrowse-api/unittests/mockDefs.h
+++ b/gentoobrowse-api/unittests/mockDefs.h
@@ -7,6 +7,7 @@
#include <portage.h>
#include <users.h>
#include <maintenance.h>
+#include <notifications.h>
#include <selectcommandUtil.impl.h>
class DLL_PUBLIC Service : public IceTray::DryIce, PQ::Mock {
@@ -25,6 +26,8 @@ class DLL_PUBLIC TestClient : public IceTray::DryIceClient {
Gentoo::MaintenancePrx m;
Gentoo::PortagePrx p;
+ Gentoo::NotificationsPrx n;
+ Gentoo::MailServerPrx ms;
Gentoo::UsersPrx u;
};
diff --git a/gentoobrowse-api/unittests/testNotifications.cpp b/gentoobrowse-api/unittests/testNotifications.cpp
new file mode 100644
index 0000000..81b28d0
--- /dev/null
+++ b/gentoobrowse-api/unittests/testNotifications.cpp
@@ -0,0 +1,102 @@
+#define BOOST_TEST_MODULE TestNotifications
+#include <boost/test/unit_test.hpp>
+
+#include "mockDefs.h"
+#include <fstream>
+
+BOOST_GLOBAL_FIXTURE( Service );
+
+BOOST_FIXTURE_TEST_SUITE(tp, TestClient);
+
+bool
+isHtml(const std::string & body)
+{
+ return body.find("<html") != std::string::npos;
+}
+
+void
+save(const std::string & name, Gentoo::EmailPtr e)
+{
+ std::ofstream text("/tmp/" + name + ".txt");
+ text << e->body[0]->payload;
+ std::ofstream html("/tmp/" + name + ".html");
+ html << e->body[1]->payload;
+}
+
+void
+commonAssert(Gentoo::EmailPtr e)
+{
+ BOOST_REQUIRE_EQUAL(e->body.size(), 2);
+ BOOST_REQUIRE_EQUAL(e->body[0]->mimetype, "text/plain");
+ BOOST_REQUIRE(!isHtml(e->body[0]->payload));
+ BOOST_REQUIRE_EQUAL(e->body[1]->mimetype, "text/html");
+ BOOST_REQUIRE(isHtml(e->body[1]->payload));
+}
+
+#if 0
+BOOST_AUTO_TEST_CASE( testSend )
+{
+ Gentoo::NewUserPtr u = new Gentoo::NewUser(1, "testuser", "Test User", "dangoodliffe@gmail.com", "some-guid");
+ auto e = n->getSignup(u);
+ BOOST_REQUIRE(e);
+ ms->sendEmail(e);
+}
+#endif
+
+BOOST_AUTO_TEST_CASE( testSignup )
+{
+ Gentoo::NewUserPtr u = new Gentoo::NewUser(1, "testuser", "Test User", "test@user.com", "some-guid");
+ auto e = n->getSignup(u);
+ BOOST_REQUIRE(e);
+ BOOST_REQUIRE_EQUAL(e->subject, "Gentoo Browse: Welcome");
+ BOOST_REQUIRE_EQUAL(e->from.name, "Gentoo Browse");
+ BOOST_REQUIRE_EQUAL(e->from.address, "noreply@gentoobrowse.randomdan.homeip.net");
+ BOOST_REQUIRE_EQUAL(e->to.name, "Test User");
+ BOOST_REQUIRE_EQUAL(e->to.address, "test@user.com");
+ commonAssert(e);
+ save("signup", e);
+ for (auto p : e->body) {
+ BOOST_REQUIRE(p->payload.find("Welcome to Gentoo Browse") != std::string::npos);
+ BOOST_REQUIRE(p->payload.find("http://gentoobrowse.randomdan.homeip.net/user/verify/some-guid") != std::string::npos);
+ BOOST_REQUIRE(p->payload.find("confirmation page: some-guid") != std::string::npos);
+ }
+}
+
+BOOST_AUTO_TEST_CASE( testNews )
+{
+ Gentoo::UserPtr u = new Gentoo::User(1, "testuser", "Test User", "test@user.com");
+ Gentoo::NewsContent nc {
+ {
+ new Gentoo::Category(1, "app-test", "Test cat1"),
+ new Gentoo::Category(2, "sys-test", "Test cat2"),
+ new Gentoo::Category(3, "no-show", "Unused cat")
+ },
+ {
+ new Gentoo::Package(1, 1, "app1", "first-seen", "Test app desc one", "Test summary 1", IceUtil::None, IceUtil::None, IceUtil::None, IceUtil::None),
+ new Gentoo::Package(2, 1, "app2", "first-seen", "Test app desc two", "Test summary 1", IceUtil::None, IceUtil::None, IceUtil::None, IceUtil::None),
+ new Gentoo::Package(3, 2, "app3", "first-seen", "Test app desc three", "Test summary 1", IceUtil::None, IceUtil::None, IceUtil::None, IceUtil::None),
+ new Gentoo::Package(4, 2, "app4", "first-seen", "Test app no-show", "Test summary 1", IceUtil::None, IceUtil::None, IceUtil::None, IceUtil::None)
+ },
+ {
+ new Gentoo::Ebuild(1, 1, 1, "1.0a", "slot", "first-seen", "last-mod", IceUtil::None),
+ new Gentoo::Ebuild(2, 1, 1, "1.1a", "slot", "first-seen", "last-mod", IceUtil::None),
+ new Gentoo::Ebuild(3, 1, 1, "1.2a", "slot", "first-seen", "last-mod", IceUtil::None),
+ new Gentoo::Ebuild(4, 2, 1, "2.0a", "slot", "first-seen", "last-mod", IceUtil::None),
+ new Gentoo::Ebuild(5, 3, 1, "3.0a", "slot", "first-seen", "last-mod", IceUtil::None),
+ new Gentoo::Ebuild(6, 2, 1, "2.1a", "slot", "first-seen", "last-mod", IceUtil::None),
+ new Gentoo::Ebuild(7, 3, 1, "3.1a", "slot", "first-seen", "last-mod", IceUtil::None)
+ }
+ };
+ auto e = n->getNews(u, nc);
+ commonAssert(e);
+ save("news", e);
+ for (auto p : e->body) {
+ BOOST_REQUIRE(p->payload.find("Latest news") != std::string::npos);
+ // BOOST_REQUIRE(p->payload.find("http://gentoobrowse.randomdan.homeip.net/packages/app-test") != std::string::npos);
+ // BOOST_REQUIRE(p->payload.find("http://gentoobrowse.randomdan.homeip.net/packages/app-test/app") != std::string::npos);
+ BOOST_REQUIRE(p->payload.find("no-show") == std::string::npos);
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+