Newer
Older
gitbucket_jkp / src / main / scala / service / WebHookService.scala
@nazoking nazoking on 27 Feb 2015 8 KB make WebHookPullRequestPayload
package service

import model.Profile._
import profile.simple._
import model.{WebHook, Account, Issue, PullRequest}
import org.slf4j.LoggerFactory
import service.RepositoryService.RepositoryInfo
import util.JGitUtil
import org.eclipse.jgit.diff.DiffEntry
import util.JGitUtil.CommitInfo
import org.eclipse.jgit.api.Git
import org.apache.http.message.BasicNameValuePair
import org.apache.http.client.entity.UrlEncodedFormEntity
import org.apache.http.NameValuePair

trait WebHookService {
  import WebHookService._

  private val logger = LoggerFactory.getLogger(classOf[WebHookService])

  def getWebHookURLs(owner: String, repository: String)(implicit s: Session): List[WebHook] =
    WebHooks.filter(_.byRepository(owner, repository)).sortBy(_.url).list

  def addWebHookURL(owner: String, repository: String, url :String)(implicit s: Session): Unit =
    WebHooks insert WebHook(owner, repository, url)

  def deleteWebHookURL(owner: String, repository: String, url :String)(implicit s: Session): Unit =
    WebHooks.filter(_.byPrimaryKey(owner, repository, url)).delete

  def callWebHook(eventName: String, webHookURLs: List[WebHook], payload: WebHookPayload): Unit = {
    import org.json4s._
    import org.json4s.jackson.Serialization
    import org.json4s.jackson.Serialization.{read, write}
    import org.apache.http.client.methods.HttpPost
    import org.apache.http.impl.client.HttpClientBuilder
    import scala.concurrent._
    import ExecutionContext.Implicits.global

    logger.debug("start callWebHook")
    implicit val formats = Serialization.formats(NoTypeHints)

    if(webHookURLs.nonEmpty){
      val json = write(payload)
      val httpClient = HttpClientBuilder.create.build

      webHookURLs.foreach { webHookUrl =>
        val f = Future {
          logger.debug(s"start web hook invocation for ${webHookUrl}")
          val httpPost = new HttpPost(webHookUrl.url)
          httpPost.addHeader("X-Github-Event", eventName)

          val params: java.util.List[NameValuePair] = new java.util.ArrayList()
          params.add(new BasicNameValuePair("payload", json))
          httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"))

          httpClient.execute(httpPost)
          httpPost.releaseConnection()
          logger.debug(s"end web hook invocation for ${webHookUrl}")
        }
        f.onSuccess {
          case s => logger.debug(s"Success: web hook request to ${webHookUrl.url}")
        }
        f.onFailure {
          case t => logger.error(s"Failed: web hook request to ${webHookUrl.url}", t)
        }
      }
    }
    logger.debug("end callWebHook")
  }

}

object WebHookService {

  trait WebHookPayload

  case class WebHookPushPayload(
    pusher: WebHookApiUser,
    ref: String,
    commits: List[WebHookCommit],
    repository: WebHookRepository
  ) extends WebHookPayload

  object WebHookPayload {
    def apply(git: Git, pusher: Account, refName: String, repositoryInfo: RepositoryInfo,
              commits: List[CommitInfo], repositoryOwner: Account): WebHookPayload =
      WebHookPushPayload(
        WebHookApiUser(pusher),
        refName,
        commits.map{ commit => WebHookCommit(git, repositoryInfo, commit) },
        WebHookRepository(
          repositoryInfo,
          owner= WebHookApiUser(repositoryOwner)
        )
      )
  }

  case class WebHookCommit(
    id: String,
    message: String,
    timestamp: String,
    url: String,
    added: List[String],
    removed: List[String],
    modified: List[String],
    author: WebHookCommitUser,
    committer: WebHookCommitUser)

  object WebHookCommit{
    def apply(git: Git, repositoryInfo: RepositoryInfo, commit: CommitInfo): WebHookCommit = {
      val diffs = JGitUtil.getDiffs(git, commit.id, false)
      val commitUrl = repositoryInfo.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/commit/" + commit.id
      WebHookCommit(
        id        = commit.id,
        message   = commit.fullMessage,
        timestamp = commit.commitTime.toString,
        url       = commitUrl,
        added     = diffs._1.collect { case x if(x.changeType == DiffEntry.ChangeType.ADD)    => x.newPath },
        removed   = diffs._1.collect { case x if(x.changeType == DiffEntry.ChangeType.DELETE) => x.oldPath },
        modified  = diffs._1.collect { case x if(x.changeType != DiffEntry.ChangeType.ADD &&
          x.changeType != DiffEntry.ChangeType.DELETE) => x.newPath },
        author    = WebHookCommitUser(
          name  = commit.authorName,
          email = commit.authorEmailAddress
        ),
        committer = WebHookCommitUser(
          name  = commit.committerName,
          email = commit.committerEmailAddress
        )
      )
    }
  }

  case class WebHookApiUser(
    login: String,
    `type`: String,
    site_admin: Boolean)

  object WebHookApiUser{
    def apply(user: Account): WebHookApiUser = WebHookApiUser(
      login      = user.fullName,
      `type`     = if(user.isGroupAccount){ "Organization" }else{ "User" },
      site_admin = user.isAdmin
    )
  }

  case class WebHookRepository(
    name: String,
    url: String,
    description: String,
    watchers: Int,
    forks: Int,
    `private`: Boolean,
    owner: WebHookApiUser)

  object WebHookRepository{
    def apply(repositoryInfo: RepositoryInfo, owner: WebHookApiUser): WebHookRepository =
      WebHookRepository(
        name        = repositoryInfo.name,
        url         = repositoryInfo.httpUrl,
        description = repositoryInfo.repository.description.getOrElse(""),
        watchers    = 0,
        forks       = repositoryInfo.forkedCount,
        `private`   = repositoryInfo.repository.isPrivate,
        owner       = 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
      )
    }
  }

  case class WebHookPullRequest(
    number: Int,
    updated_at: String,
    head: WebHookPullRequestCommit,
    base: WebHookPullRequestCommit,
    mergeable: Option[Boolean],
    title: String,
    body: String,
    user: WebHookApiUser,
    url: String)

  object WebHookPullRequest{
    def apply(issue: Issue, pullRequest: PullRequest, headRepo: WebHookRepository, baseRepo: WebHookRepository, user: WebHookApiUser): WebHookPullRequest = WebHookPullRequest(
        number     = issue.issueId,
        updated_at = issue.updatedDate.toString(),
        head       = WebHookPullRequestCommit(
                       sha  = pullRequest.commitIdTo,
                       ref  = pullRequest.requestBranch,
                       repo = headRepo),
        base       = WebHookPullRequestCommit(
                       sha  = pullRequest.commitIdFrom,
                       ref  = pullRequest.branch,
                       repo = baseRepo),
        mergeable  = None,
        title      = issue.title,
        body       = issue.content.getOrElse(""),
        user       = user,
        url        = s"${baseRepo.url}/pulls/${issue.issueId}"
      )
  }

  case class WebHookPullRequestCommit(
    label: String,
    sha: String,
    ref: String,
    repo: WebHookRepository,
    user: WebHookApiUser)

  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)
  }
}