diff --git a/src/main/scala/app/IssuesController.scala b/src/main/scala/app/IssuesController.scala index fab8b75..67bbc5b 100644 --- a/src/main/scala/app/IssuesController.scala +++ b/src/main/scala/app/IssuesController.scala @@ -4,7 +4,7 @@ import service._ import IssuesService._ -import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, ReadableUsersAuthenticator} +import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, ReadableUsersAuthenticator, Notifier} import org.scalatra.Ok class IssuesController extends IssuesControllerBase @@ -112,7 +112,14 @@ // record activity recordCreateIssueActivity(owner, name, userName, issueId, form.title) - redirect(s"/${owner}/${name}/issues/${issueId}") + val uri = s"/${owner}/${name}/issues/${issueId}" + + // notifications + Notifier().toNotify(repository, issueId, form.content.getOrElse("")){ + Notifier.msgIssue(baseUrl + uri) + } + + redirect(uri) }) ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (form, repository) => diff --git a/src/main/scala/servlet/TransactionFilter.scala b/src/main/scala/servlet/TransactionFilter.scala index 5a26734..0e96273 100644 --- a/src/main/scala/servlet/TransactionFilter.scala +++ b/src/main/scala/servlet/TransactionFilter.scala @@ -3,7 +3,6 @@ import javax.servlet._ import org.slf4j.LoggerFactory import javax.servlet.http.HttpServletRequest -import scala.slick.session.Database /** * Controls the transaction with the open session in view pattern. @@ -21,15 +20,19 @@ // assets don't need transaction chain.doFilter(req, res) } else { - val context = req.getServletContext - Database.forURL(context.getInitParameter("db.url"), - context.getInitParameter("db.user"), - context.getInitParameter("db.password")) withTransaction { + Database(req.getServletContext) withTransaction { logger.debug("TODO begin transaction") chain.doFilter(req, res) logger.debug("TODO end transaction") } } } - -} \ No newline at end of file + +} + +object Database { + def apply(context: ServletContext): scala.slick.session.Database = + scala.slick.session.Database.forURL(context.getInitParameter("db.url"), + context.getInitParameter("db.user"), + context.getInitParameter("db.password")) +} diff --git a/src/main/scala/util/Notifier.scala b/src/main/scala/util/Notifier.scala index dab8eb3..c8e6821 100644 --- a/src/main/scala/util/Notifier.scala +++ b/src/main/scala/util/Notifier.scala @@ -1,37 +1,104 @@ package util -import org.apache.commons.mail.{DefaultAuthenticator, SimpleEmail} +import scala.concurrent._ +import ExecutionContext.Implicits.global +import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail} +import org.slf4j.LoggerFactory -import service.SystemSettingsService.{SystemSettings, Smtp} +import app.Context +import service.{AccountService, RepositoryService, IssuesService, SystemSettingsService} +import servlet.Database +import SystemSettingsService.Smtp -trait Notifier { +trait Notifier extends RepositoryService with AccountService with IssuesService { + def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String) + (msg: String => String)(implicit context: Context): Unit + + protected def recipients(issue: model.Issue)(notify: String => Unit)(implicit context: Context) = + ( + // individual repository's owner + issue.userName :: + // collaborators + getCollaborators(issue.userName, issue.repositoryName) ::: + // participants + issue.openedUserName :: + getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName) + ) + .distinct + .withFilter ( _ != context.loginAccount.get.userName ) // the operation in person is excluded + .foreach ( getAccountByUserName(_) foreach (x => notify(x.mailAddress)) ) } object Notifier { - def apply(settings: SystemSettings) = { - new Mailer(settings.smtp.get) + // TODO We want to be able to switch to mock. + def apply(): Notifier = new SystemSettingsService {}.loadSystemSettings match { + case settings if settings.notification => new Mailer(settings.smtp.get) + case _ => new MockMailer } + def msgIssue(url: String) = (content: String) => s""" + |${content}
+ |--
+ |View it on GitBucket + """.stripMargin + + def msgPullRequest(url: String) = (content: String) => s""" + |${content}
+ |View, comment on, or merge it at:
+ |${url} + """.stripMargin + + def msgComment(url: String) = (content: String) => s""" + |${content}
+ |--
+ |View it on GitBucket + """.stripMargin + + def msgStatus(id: Int, url: String) = (content: String) => s""" + |${content} #${id} + """.stripMargin } -class Mailer(val smtp: Smtp) extends Notifier { - def notifyTo(issue: model.Issue) = { - val email = new SimpleEmail - email.setHostName(smtp.host) - email.setSmtpPort(smtp.port.get) - smtp.user.foreach { user => - email.setAuthenticator(new DefaultAuthenticator(user, smtp.password.getOrElse(""))) - } - smtp.ssl.foreach { ssl => - email.setSSLOnConnect(ssl) - } - email.setFrom("TODO address", "TODO name") - email.addTo("TODO") - email.setSubject(s"[${issue.repositoryName}] ${issue.title} (#${issue.issueId})") - email.setMsg("TODO") +class Mailer(private val smtp: Smtp) extends Notifier { + private val logger = LoggerFactory.getLogger(classOf[Mailer]) - email.send + def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String) + (msg: String => String)(implicit context: Context) = { + val f = future { + val email = new HtmlEmail + email.setHostName(smtp.host) + email.setSmtpPort(smtp.port.get) + smtp.user.foreach { user => + email.setAuthenticator(new DefaultAuthenticator(user, smtp.password.getOrElse(""))) + } + smtp.ssl.foreach { ssl => + email.setSSLOnConnect(ssl) + } + email.setFrom("notifications@gitbucket.com", context.loginAccount.get.userName) + email.setHtmlMsg(msg(view.Markdown.toHtml(content, r, false, true))) + + // TODO Can we use the Database Session in other than Transaction Filter? + Database(context.request.getServletContext) withSession { + getIssue(r.owner, r.name, issueId.toString) foreach { issue => + email.setSubject(s"[${r.name}] ${issue.title} (#${issueId})") + recipients(issue) { + email.getToAddresses.clear + email.addTo(_).send + } + } + } + "Notifications Successful." + } + f onSuccess { + case s => logger.debug(s) + } + f onFailure { + case t => logger.error("Notifications Failed.", t) + } } } -class MockMailer extends Notifier \ No newline at end of file +class MockMailer extends Notifier { + def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String) + (msg: String => String)(implicit context: Context): Unit = {} +} \ No newline at end of file