diff --git a/src/main/scala/app/IssuesController.scala b/src/main/scala/app/IssuesController.scala index b52b223..3dd3ec7 100644 --- a/src/main/scala/app/IssuesController.scala +++ b/src/main/scala/app/IssuesController.scala @@ -12,11 +12,11 @@ class IssuesController extends IssuesControllerBase with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService - with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator + with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService trait IssuesControllerBase extends ControllerBase { self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService - with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator => + with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService => case class IssueCreateForm(title: String, content: Option[String], assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String]) @@ -109,9 +109,12 @@ // record activity recordCreateIssueActivity(owner, name, userName, issueId, form.title) - // extract references and create refer comment getIssue(owner, name, issueId.toString).foreach { issue => + // extract references and create refer comment createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse("")) + + // call web hooks + callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get) } // notifications @@ -364,6 +367,22 @@ createReferComment(owner, name, issue, content) } + // call web hooks + action match { + case None => callIssueCommentWebHook(repository, issue, commentId, context.loginAccount.get) + case Some(act) => val webHookAction = act match { + case "open" => "opened" + case "reopen" => "reopened" + case "close" => "closed" + case _ => act + } + if(issue.isPullRequest){ + callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, context.loginAccount.get) + } else { + callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, context.loginAccount.get) + } + } + // notifications Notifier() match { case f => @@ -416,5 +435,4 @@ hasWritePermission(owner, repoName, context.loginAccount)) } } - } diff --git a/src/main/scala/app/PullRequestsController.scala b/src/main/scala/app/PullRequestsController.scala index 603f8d7..7956199 100644 --- a/src/main/scala/app/PullRequestsController.scala +++ b/src/main/scala/app/PullRequestsController.scala @@ -23,11 +23,11 @@ class PullRequestsController extends PullRequestsControllerBase with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService - with CommitsService with ActivityService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator + with CommitsService with ActivityService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator trait PullRequestsControllerBase extends ControllerBase { self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService - with CommitsService with ActivityService with PullRequestService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator => + with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator => private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase]) @@ -193,9 +193,7 @@ closeIssuesFromMessage(form.message, loginAccount.userName, owner, name) } // call web hook - getPullRequest(repository.owner, repository.name, issueId).map{ case (issue, pr) => - callPullRequestWebHook("closed", repository, issue, pr, context.baseUrl, context.loginAccount.get) - } + callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get) // notifications Notifier().toNotify(repository, issueId, "merge"){ @@ -354,9 +352,7 @@ recordPullRequestActivity(repository.owner, repository.name, loginUserName, issueId, form.title) // call web hook - getPullRequest(repository.owner, repository.name, issueId).map{ case (issue, pr) => - callPullRequestWebHook("opend", repository, issue, pr, context.baseUrl, context.loginAccount.get) - } + callPullRequestWebHook("opend", repository, issueId, context.baseUrl, context.loginAccount.get) // notifications Notifier().toNotify(repository, issueId, form.content.getOrElse("")){ @@ -480,31 +476,4 @@ repository, hasWritePermission(owner, repoName, context.loginAccount)) } - - private def callPullRequestWebHook(action: String, repository: RepositoryService.RepositoryInfo, issue: Issue, pullRequest: PullRequest, baseUrl: String, sender: model.Account): Unit = { - import WebHookService._ - getWebHookURLs(repository.owner, repository.name) match { - case webHookURLs if(webHookURLs.nonEmpty) => - def findOwner(owner: String, knowns: Seq[model.Account]): Option[model.Account] = knowns.find(_.fullName == owner).orElse(getAccountByUserName(owner)) - for{ - baseOwner <- findOwner(repository.owner, Seq(sender)) - headOwner <- findOwner(pullRequest.requestUserName, Seq(sender, baseOwner)) - headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName, baseUrl) - } yield { - val payload = WebHookPullRequestPayload( - action = action, - issue = issue, - pullRequest = pullRequest, - headRepository = headRepo, - headOwner = headOwner, - baseRepository = repository, - baseOwner = baseOwner, - sender = sender) - for(ownerAccount <- getAccountByUserName(repository.owner)){ - callWebHook("pull_request", webHookURLs, payload) - } - } - case _ => - } - } } diff --git a/src/main/scala/app/RepositorySettingsController.scala b/src/main/scala/app/RepositorySettingsController.scala index a396801..233b1ef 100644 --- a/src/main/scala/app/RepositorySettingsController.scala +++ b/src/main/scala/app/RepositorySettingsController.scala @@ -7,7 +7,7 @@ import jp.sf.amateras.scalatra.forms._ import org.apache.commons.io.FileUtils import org.scalatra.i18n.Messages -import service.WebHookService.WebHookPayload +import service.WebHookService.WebHookPushPayload import util.JGitUtil.CommitInfo import util.ControlUtil._ import org.eclipse.jgit.api.Git @@ -168,7 +168,7 @@ getAccountByUserName(repository.owner).foreach { ownerAccount => callWebHook("push", List(model.WebHook(repository.owner, repository.name, form.url)), - WebHookPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount) + WebHookPushPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount) ) } flash += "url" -> form.url diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index e288c98..bc6339b 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -17,7 +17,7 @@ import jp.sf.amateras.scalatra.forms._ import org.eclipse.jgit.dircache.DirCache import org.eclipse.jgit.revwalk.RevCommit -import service.WebHookService.WebHookPayload +import service.WebHookService.WebHookPushPayload class RepositoryViewerController extends RepositoryViewerControllerBase with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService @@ -504,13 +504,10 @@ // call web hook val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)) - getWebHookURLs(repository.owner, repository.name) match { - case webHookURLs if(webHookURLs.nonEmpty) => - for(ownerAccount <- getAccountByUserName(repository.owner)){ - callWebHook("push", webHookURLs, - WebHookPayload(git, loginAccount, headName, repository, List(commit), ownerAccount)) - } - case _ => + callWebHookOf(repository.owner, repository.name, "push") { + getAccountByUserName(repository.owner).map{ ownerAccount => + WebHookPushPayload(git, loginAccount, headName, repository, List(commit), ownerAccount) + } } } } diff --git a/src/main/scala/service/AccountService.scala b/src/main/scala/service/AccountService.scala index c502eb7..88d95fa 100644 --- a/src/main/scala/service/AccountService.scala +++ b/src/main/scala/service/AccountService.scala @@ -77,6 +77,16 @@ def getAccountByUserName(userName: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] = Accounts filter(t => (t.userName === userName.bind) && (t.removed === false.bind, !includeRemoved)) firstOption + def getAccountsByUserNames(userNames: Set[String], knowns:Set[Account], includeRemoved: Boolean = false)(implicit s: Session): Map[String, Account] = { + val map = knowns.map(a => a.userName -> a).toMap + val needs = userNames -- map.keySet + if(needs.isEmpty){ + map + }else{ + map ++ Accounts.filter(t => (t.userName inSetBind needs) && (t.removed === false.bind, !includeRemoved)).list.map(a => a.userName -> a).toMap + } + } + def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] = Accounts filter(t => (t.mailAddress.toLowerCase === mailAddress.toLowerCase.bind) && (t.removed === false.bind, !includeRemoved)) firstOption diff --git a/src/main/scala/service/WebHookService.scala b/src/main/scala/service/WebHookService.scala index 2947545..92f1952 100644 --- a/src/main/scala/service/WebHookService.scala +++ b/src/main/scala/service/WebHookService.scala @@ -2,7 +2,7 @@ import model.Profile._ import profile.simple._ -import model.{WebHook, Account, Issue, PullRequest} +import model.{WebHook, Account, Issue, PullRequest, IssueComment} import org.slf4j.LoggerFactory import service.RepositoryService.RepositoryInfo import util.JGitUtil @@ -12,6 +12,7 @@ import org.apache.http.message.BasicNameValuePair import org.apache.http.client.entity.UrlEncodedFormEntity import org.apache.http.NameValuePair +import java.util.Date trait WebHookService { import WebHookService._ @@ -27,6 +28,13 @@ def deleteWebHookURL(owner: String, repository: String, url :String)(implicit s: Session): Unit = WebHooks.filter(_.byPrimaryKey(owner, repository, url)).delete + def callWebHookOf(owner: String, repository: String, eventName: String)(makePayload: => Option[WebHookPayload])(implicit s: Session): Unit = { + val webHookURLs = getWebHookURLs(owner, repository) + if(webHookURLs.nonEmpty){ + makePayload.map(callWebHook(eventName, webHookURLs, _)) + } + } + def callWebHook(eventName: String, webHookURLs: List[WebHook], payload: WebHookPayload): Unit = { import org.json4s._ import org.json4s.jackson.Serialization @@ -67,13 +75,85 @@ } logger.debug("end callWebHook") } +} + +trait WebHookPullRequestService extends WebHookService { + self: AccountService with RepositoryService with PullRequestService with IssuesService => + + // https://developer.github.com/v3/activity/events/types/#issuesevent + def callIssuesWebHook(action: String, repository: RepositoryService.RepositoryInfo, issue: Issue, baseUrl: String, sender: model.Account)(implicit s: Session): Unit = { + import WebHookService._ + callWebHookOf(repository.owner, repository.name, "issues"){ + val users = getAccountsByUserNames(Set(repository.owner, issue.userName), Set(sender)) + for{ + repoOwner <- users.get(repository.owner) + issueUser <- users.get(issue.userName) + } yield { + WebHookIssuesPayload( + action = action, + number = issue.issueId, + repository = WebHookRepository(repository, WebHookApiUser(repoOwner)), + issue = WebHookIssue(issue, WebHookApiUser(issueUser)), + sender = WebHookApiUser(sender)) + } + } + } + + def callPullRequestWebHook(action: String, repository: RepositoryService.RepositoryInfo, issueId: Int, baseUrl: String, sender: model.Account)(implicit s: Session): Unit = { + import WebHookService._ + callWebHookOf(repository.owner, repository.name, "pull_request"){ + for{ + (issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId) + users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName), Set(sender)) + baseOwner <- users.get(repository.owner) + headOwner <- users.get(pullRequest.requestUserName) + headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName, baseUrl) + } yield { + WebHookPullRequestPayload( + action = action, + issue = issue, + pullRequest = pullRequest, + headRepository = headRepo, + headOwner = headOwner, + baseRepository = repository, + baseOwner = baseOwner, + sender = sender) + } + } + } +} +trait WebHookIssueCommentService extends WebHookPullRequestService { + self: AccountService with RepositoryService with PullRequestService with IssuesService => + + def callIssueCommentWebHook(repository: RepositoryService.RepositoryInfo, issue: Issue, issueCommentId: Int, sender: model.Account)(implicit s: Session): Unit = { + import WebHookService._ + callWebHookOf(repository.owner, repository.name, "issue_comment"){ + for{ + issueComment <- getComment(repository.owner, repository.name, issueCommentId.toString()) + users = getAccountsByUserNames(Set(issue.userName, repository.owner, issueComment.userName), Set(sender)) + issueUser <- users.get(issue.userName) + repoOwner <- users.get(repository.owner) + commenter <- users.get(issueComment.userName) + } yield { + WebHookIssueCommentPayload( + issue = issue, + issueUser = issueUser, + comment = issueComment, + commentUser = commenter, + repository = repository, + repositoryUser = repoOwner, + sender = sender) + } + } + } } object WebHookService { trait WebHookPayload + // https://developer.github.com/v3/activity/events/types/#pushevent case class WebHookPushPayload( pusher: WebHookApiUser, ref: String, @@ -81,9 +161,9 @@ repository: WebHookRepository ) extends WebHookPayload - object WebHookPayload { + object WebHookPushPayload { def apply(git: Git, pusher: Account, refName: String, repositoryInfo: RepositoryInfo, - commits: List[CommitInfo], repositoryOwner: Account): WebHookPayload = + commits: List[CommitInfo], repositoryOwner: Account): WebHookPushPayload = WebHookPushPayload( WebHookApiUser(pusher), refName, @@ -95,10 +175,76 @@ ) } + // https://developer.github.com/v3/activity/events/types/#issuesevent + case class WebHookIssuesPayload( + action: String, + number: Int, + repository: WebHookRepository, + issue: WebHookIssue, + sender: WebHookApiUser) extends WebHookPayload + + // https://developer.github.com/v3/activity/events/types/#pullrequestevent + case class WebHookPullRequestPayload( + action: String, + number: Int, + repository: WebHookRepository, + pull_request: WebHookPullRequest, + sender: WebHookApiUser + ) extends WebHookPayload + + object WebHookPullRequestPayload{ + def apply(action: String, + issue: Issue, + pullRequest: PullRequest, + headRepository: RepositoryInfo, + headOwner: Account, + baseRepository: RepositoryInfo, + baseOwner: Account, + sender: model.Account): WebHookPullRequestPayload = { + val headRepoPayload = WebHookRepository(headRepository, headOwner) + val baseRepoPayload = WebHookRepository(baseRepository, baseOwner) + val senderPayload = WebHookApiUser(sender) + val pr = WebHookPullRequest(issue, pullRequest, headRepoPayload, baseRepoPayload, senderPayload) + WebHookPullRequestPayload( + action = action, + number = issue.issueId, + repository = pr.base.repo, + pull_request = pr, + sender = senderPayload + ) + } + } + + // https://developer.github.com/v3/activity/events/types/#issuecommentevent + case class WebHookIssueCommentPayload( + action: String, + repository: WebHookRepository, + issue: WebHookIssue, + comment: WebHookComment, + sender: WebHookApiUser + ) extends WebHookPayload + + object WebHookIssueCommentPayload{ + def apply( + issue: Issue, + issueUser: Account, + comment: IssueComment, + commentUser: Account, + repository: RepositoryInfo, + repositoryUser: Account, + sender: Account): WebHookIssueCommentPayload = + WebHookIssueCommentPayload( + action = "created", + repository = WebHookRepository(repository, repositoryUser), + issue = WebHookIssue(issue, WebHookApiUser(issueUser)), + comment = WebHookComment(comment, WebHookApiUser(commentUser)), + sender = WebHookApiUser(sender)) + } + case class WebHookCommit( id: String, message: String, - timestamp: String, + timestamp: String, // "2014-10-09T17:10:36-07:00", url: String, added: List[String], removed: List[String], @@ -144,12 +290,17 @@ ) } + case class WebHookCommitUser( + name: String, + email: String) + + // https://developer.github.com/v3/repos/ case class WebHookRepository( name: String, url: String, description: String, watchers: Int, - forks: Int, + forks: Int, // forks_count `private`: Boolean, owner: WebHookApiUser) @@ -164,46 +315,15 @@ `private` = repositoryInfo.repository.isPrivate, owner = owner ) + def apply(repositoryInfo: RepositoryInfo, owner: Account): WebHookRepository = + this(repositoryInfo, WebHookApiUser(owner)) } - case class WebHookCommitUser( - name: String, - email: String) - - case class WebHookPullRequestPayload( - val action: String, - val number: Int, - val repository: WebHookRepository, - val pull_request: WebHookPullRequest, - val sender: WebHookApiUser - ) extends WebHookPayload - - object WebHookPullRequestPayload{ - def apply(action: String, - issue: Issue, - pullRequest: PullRequest, - headRepository: RepositoryInfo, - headOwner: Account, - baseRepository: RepositoryInfo, - baseOwner: Account, - sender: model.Account): WebHookPullRequestPayload = { - val headRepoPayload = WebHookRepository(headRepository, owner=WebHookApiUser(headOwner)) - val baseRepoPayload = WebHookRepository(baseRepository, owner=WebHookApiUser(baseOwner)) - val senderPayload = WebHookApiUser(sender) - val pr = WebHookPullRequest(issue, pullRequest, headRepoPayload, baseRepoPayload, senderPayload) - WebHookPullRequestPayload( - action = action, - number = issue.issueId, - repository = pr.base.repo, - pull_request = pr, - sender = senderPayload - ) - } - } - + // https://developer.github.com/v3/pulls/ case class WebHookPullRequest( number: Int, - updated_at: String, + updated_at: Date, + created_at: Date, head: WebHookPullRequestCommit, base: WebHookPullRequestCommit, mergeable: Option[Boolean], @@ -215,7 +335,8 @@ object WebHookPullRequest{ def apply(issue: Issue, pullRequest: PullRequest, headRepo: WebHookRepository, baseRepo: WebHookRepository, user: WebHookApiUser): WebHookPullRequest = WebHookPullRequest( number = issue.issueId, - updated_at = issue.updatedDate.toString(), + updated_at = issue.updatedDate, + created_at = issue.registeredDate, head = WebHookPullRequestCommit( sha = pullRequest.commitIdTo, ref = pullRequest.requestBranch, @@ -242,11 +363,53 @@ object WebHookPullRequestCommit{ def apply(sha: String, ref: String, - repo: WebHookRepository): WebHookPullRequestCommit = WebHookPullRequestCommit( - label = s"${repo.owner.login}:${ref}", - sha = sha, - ref = ref, - repo = repo, - user = repo.owner) + repo: WebHookRepository): WebHookPullRequestCommit = + WebHookPullRequestCommit( + label = s"${repo.owner.login}:${ref}", + sha = sha, + ref = ref, + repo = repo, + user = repo.owner) + } + + // https://developer.github.com/v3/issues/ + case class WebHookIssue( + number: Int, + title: String, + user: WebHookApiUser, + // labels, + state: String, + created_at: Date, + updated_at: Date, + body: String) + + object WebHookIssue{ + def apply(issue: Issue, user: WebHookApiUser): WebHookIssue = + WebHookIssue( + number = issue.issueId, + title = issue.title, + user = user, + state = if(issue.closed){ "closed" }else{ "open" }, + body = issue.content.getOrElse(""), + created_at = issue.registeredDate, + updated_at = issue.updatedDate) + } + + // https://developer.github.com/v3/issues/comments/ + case class WebHookComment( + id: Int, + user: WebHookApiUser, + body: String, + created_at: Date, + updated_at: Date) + + object WebHookComment{ + def apply(comment: IssueComment, user: WebHookApiUser): WebHookComment = + WebHookComment( + id = comment.commentId, + user = user, + body = comment.content, + created_at = comment.registeredDate, + updated_at = comment.updatedDate) } } diff --git a/src/main/scala/servlet/GitRepositoryServlet.scala b/src/main/scala/servlet/GitRepositoryServlet.scala index 0dfd774..b9273a7 100644 --- a/src/main/scala/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/servlet/GitRepositoryServlet.scala @@ -180,15 +180,12 @@ } // call web hook - getWebHookURLs(owner, repository) match { - case webHookURLs if(webHookURLs.nonEmpty) => - for(pusherAccount <- getAccountByUserName(pusher); - ownerAccount <- getAccountByUserName(owner); - repositoryInfo <- getRepository(owner, repository, baseUrl)){ - callWebHook("push", webHookURLs, - WebHookPayload(git, pusherAccount, command.getRefName, repositoryInfo, newCommits, ownerAccount)) - } - case _ => + callWebHookOf(owner, repository, "push"){ + for(pusherAccount <- getAccountByUserName(pusher); + ownerAccount <- getAccountByUserName(owner); + repositoryInfo <- getRepository(owner, repository, baseUrl)) yield { + WebHookPushPayload(git, pusherAccount, command.getRefName, repositoryInfo, newCommits, ownerAccount) + } } } }