diff --git a/src/main/resources/update/gitbucket-core_4.11.xml b/src/main/resources/update/gitbucket-core_4.11.xml
index 1c41d88..6026a95 100644
--- a/src/main/resources/update/gitbucket-core_4.11.xml
+++ b/src/main/resources/update/gitbucket-core_4.11.xml
@@ -11,4 +11,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/scala/gitbucket/core/api/ApiRepository.scala b/src/main/scala/gitbucket/core/api/ApiRepository.scala
index a6c5bd5..1f79072 100644
--- a/src/main/scala/gitbucket/core/api/ApiRepository.scala
+++ b/src/main/scala/gitbucket/core/api/ApiRepository.scala
@@ -53,4 +53,14 @@
def forPushPayload(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
ApiRepository(repositoryInfo.repository, owner, forkedCount=repositoryInfo.forkedCount, urlIsHtmlUrl=true)
+ def forDummyPayload(owner: ApiUser): ApiRepository =
+ ApiRepository(
+ name="dummy",
+ full_name=s"${owner.login}/dummy",
+ description="",
+ watchers=0,
+ forks=0,
+ `private`=false,
+ default_branch="master",
+ owner=owner)(true)
}
diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala
index 045fbfe..1ca84b7 100644
--- a/src/main/scala/gitbucket/core/controller/AccountController.scala
+++ b/src/main/scala/gitbucket/core/controller/AccountController.scala
@@ -2,9 +2,10 @@
import gitbucket.core.account.html
import gitbucket.core.helper
-import gitbucket.core.model.{GroupMember, Role}
+import gitbucket.core.model.{GroupMember, Role, WebHook, WebHookContentType, AccountWebHook, RepositoryWebHook, RepositoryWebHookEvent}
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service._
+import gitbucket.core.service.WebHookService._
import gitbucket.core.ssh.SshUtil
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Directory._
@@ -16,7 +17,6 @@
import org.scalatra.i18n.Messages
import org.scalatra.BadRequest
-
class AccountController extends AccountControllerBase
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
@@ -109,6 +109,47 @@
"account" -> trim(label("Group/User name", text(required, validAccountName)))
)(AccountForm.apply)
+ // for account web hook url addition.
+ case class AccountWebHookForm(url: String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])
+
+ def accountWebHookForm(update:Boolean) = mapping(
+ "url" -> trim(label("url", text(required, accountWebHook(update)))),
+ "events" -> accountWebhookEvents,
+ "ctype" -> label("ctype", text()),
+ "token" -> optional(trim(label("token", text(maxlength(100)))))
+ )(
+ (url, events, ctype, token) => AccountWebHookForm(url, events, WebHookContentType.valueOf(ctype), token)
+ )
+ /**
+ * Provides duplication check for web hook url. duplicated from RepositorySettingsController.scala
+ */
+ private def accountWebHook(needExists: Boolean): Constraint = new Constraint(){
+ override def validate(name: String, value: String, messages: Messages): Option[String] =
+ if(getAccountWebHook(params("userName"), value).isDefined != needExists){
+ Some(if(needExists){
+ "URL had not been registered yet."
+ } else {
+ "URL had been registered already."
+ })
+ } else {
+ None
+ }
+ }
+
+ private def accountWebhookEvents = new ValueType[Set[WebHook.Event]]{
+ def convert(name: String, params: Map[String, String], messages: Messages): Set[WebHook.Event] = {
+ WebHook.Event.values.flatMap { t =>
+ params.get(name + "." + t.name).map(_ => t)
+ }.toSet
+ }
+ def validate(name: String, params: Map[String, String], messages: Messages): Seq[(String, String)] = if(convert(name,params,messages).isEmpty){
+ Seq(name -> messages("error.required").format(name))
+ } else {
+ Nil
+ }
+ }
+
+
/**
* Displays user information.
*/
@@ -129,6 +170,13 @@
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
}
+ // Webhooks
+ case "webhooks" =>
+ gitbucket.core.account.html.webhook(account,
+ if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
+ getAccountWebHooks(account.userName)
+ )
+
// Repositories
case _ => {
val members = getGroupMembers(account.userName)
@@ -273,6 +321,106 @@
redirect(s"/${userName}/_application")
})
+ /**
+ * Display the account web hook edit page.
+ */
+ get("/:userName/_hooks/new")(oneselfOnly {
+ val userName = params("userName")
+ getAccountByUserName(userName).map { account =>
+ val webhook = AccountWebHook(userName, "", WebHookContentType.FORM, None)
+ html.edithooks(webhook, Set(WebHook.Push), account, if (account.isGroupAccount) Nil else getGroupsByUserName(userName), flash.get("info"), true)
+ }
+ })
+
+ /**
+ * Add the account web hook URL.
+ */
+ post("/:userName/_hooks/new", accountWebHookForm(false))(oneselfOnly { form =>
+ val userName = params("userName")
+ addAccountWebHook(userName, form.url, form.events, form.ctype, form.token)
+ flash += "info" -> s"Webhook ${form.url} created"
+ redirect(s"/${userName}?tab=webhooks")
+ })
+
+ /**
+ * Delete the account web hook URL.
+ */
+ get("/:userName/_hooks/delete")(oneselfOnly {
+ val userName = params("userName")
+ deleteAccountWebHook(userName, params("url"))
+ flash += "info" -> s"Webhook ${params("url")} deleted"
+ redirect(s"/${userName}?tab=webhooks")
+ })
+
+ /**
+ * Display the account web hook edit page.
+ */
+ get("/:userName/_hooks/edit")(oneselfOnly {
+ val userName = params("userName")
+ getAccountByUserName(userName).map { account =>
+ getAccountWebHook(userName, params("url")).map { case (webhook, events) =>
+ html.edithooks(webhook, events, account, if (account.isGroupAccount) Nil else getGroupsByUserName(userName), flash.get("info"), false)
+ } getOrElse NotFound()
+ }
+ })
+
+ /**
+ * Update account web hook settings.
+ */
+ post("/:userName/_hooks/edit", accountWebHookForm(true))(oneselfOnly { form =>
+ val userName = params("userName")
+ updateAccountWebHook(userName, form.url, form.events, form.ctype, form.token)
+ flash += "info" -> s"webhook ${form.url} updated"
+ redirect(s"/${userName}?tab=webhooks")
+ })
+
+ /**
+ * Send the test request to registered account web hook URLs.
+ */
+ ajaxPost("/:userName/_hooks/test")(oneselfOnly {
+ import scala.collection.JavaConverters._
+ import scala.concurrent.duration._
+ import scala.concurrent._
+ import scala.util.control.NonFatal
+ import org.apache.http.util.EntityUtils
+ import scala.concurrent.ExecutionContext.Implicits.global
+
+ def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h => Array(h.getName, h.getValue) }
+
+ val userName = params("userName")
+ val url = params("url")
+ val token = Some(params("token"))
+ val ctype = WebHookContentType.valueOf(params("ctype"))
+ val dummyWebHookInfo = RepositoryWebHook(userName, "dummy", url, ctype, token)
+ val dummyPayload = {
+ val ownerAccount = getAccountByUserName(userName).get
+ WebHookPushPayload.createDummyPayload(ownerAccount)
+ }
+
+ val (webHook, json, reqFuture, resFuture) = callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload).head
+
+ val toErrorMap: PartialFunction[Throwable, Map[String,String]] = {
+ case e: java.net.UnknownHostException => Map("error"-> ("Unknown host " + e.getMessage))
+ case e: java.lang.IllegalArgumentException => Map("error"-> ("invalid url"))
+ case e: org.apache.http.client.ClientProtocolException => Map("error"-> ("invalid url"))
+ case NonFatal(e) => Map("error"-> (e.getClass + " "+ e.getMessage))
+ }
+
+ contentType = formats("json")
+ org.json4s.jackson.Serialization.write(Map(
+ "url" -> url,
+ "request" -> Await.result(reqFuture.map(req => Map(
+ "headers" -> _headers(req.getAllHeaders),
+ "payload" -> json
+ )).recover(toErrorMap), 20 seconds),
+ "responce" -> Await.result(resFuture.map(res => Map(
+ "status" -> res.getStatusLine(),
+ "body" -> EntityUtils.toString(res.getEntity()),
+ "headers" -> _headers(res.getAllHeaders())
+ )).recover(toErrorMap), 20 seconds)
+ ))
+ })
+
get("/register"){
if(context.settings.allowAccountRegistration){
if(context.loginAccount.isDefined){
diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
index 4fc7974..6a2795f 100644
--- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
+++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
@@ -1,7 +1,7 @@
package gitbucket.core.controller
import gitbucket.core.settings.html
-import gitbucket.core.model.WebHook
+import gitbucket.core.model.{WebHook, RepositoryWebHook}
import gitbucket.core.service._
import gitbucket.core.service.WebHookService._
import gitbucket.core.util._
@@ -221,7 +221,7 @@
* Display the web hook edit page.
*/
get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository =>
- val webhook = WebHook(repository.owner, repository.name, "", WebHookContentType.FORM, None)
+ val webhook = RepositoryWebHook(repository.owner, repository.name, "", WebHookContentType.FORM, None)
html.edithooks(webhook, Set(WebHook.Push), repository, flash.get("info"), true)
})
@@ -260,7 +260,7 @@
val url = params("url")
val token = Some(params("token"))
val ctype = WebHookContentType.valueOf(params("ctype"))
- val dummyWebHookInfo = WebHook(repository.owner, repository.name, url, ctype, token)
+ val dummyWebHookInfo = RepositoryWebHook(repository.owner, repository.name, url, ctype, token)
val dummyPayload = {
val ownerAccount = getAccountByUserName(repository.owner).get
val commits = if(JGitUtil.isEmpty(git)) List.empty else git.log
diff --git a/src/main/scala/gitbucket/core/controller/WikiController.scala b/src/main/scala/gitbucket/core/controller/WikiController.scala
index b32deb0..d3f949f 100644
--- a/src/main/scala/gitbucket/core/controller/WikiController.scala
+++ b/src/main/scala/gitbucket/core/controller/WikiController.scala
@@ -1,6 +1,6 @@
package gitbucket.core.controller
-import gitbucket.core.model.{WebHook, WebHookEvent}
+import gitbucket.core.model.WebHook
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.WebHookService.WebHookGollumPayload
import gitbucket.core.wiki.html
diff --git a/src/main/scala/gitbucket/core/model/AccountWebHook.scala b/src/main/scala/gitbucket/core/model/AccountWebHook.scala
new file mode 100644
index 0000000..df28993
--- /dev/null
+++ b/src/main/scala/gitbucket/core/model/AccountWebHook.scala
@@ -0,0 +1,25 @@
+package gitbucket.core.model
+
+trait AccountWebHookComponent extends TemplateComponent { self: Profile =>
+ import profile.api._
+
+ private implicit val whContentTypeColumnType = MappedColumnType.base[WebHookContentType, String](whct => whct.code , code => WebHookContentType.valueOf(code))
+
+ lazy val AccountWebHooks = TableQuery[AccountWebHooks]
+
+ class AccountWebHooks(tag: Tag) extends Table[AccountWebHook](tag, "ACCOUNT_WEB_HOOK") with BasicTemplate {
+ val url = column[String]("URL")
+ val token = column[Option[String]]("TOKEN")
+ val ctype = column[WebHookContentType]("CTYPE")
+ def * = (userName, url, ctype, token) <> ((AccountWebHook.apply _).tupled, AccountWebHook.unapply)
+
+ def byPrimaryKey(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind)
+ }
+}
+
+case class AccountWebHook(
+ userName: String,
+ url: String,
+ ctype: WebHookContentType,
+ token: Option[String]
+) extends WebHook
diff --git a/src/main/scala/gitbucket/core/model/AccountWebHookEvent.scala b/src/main/scala/gitbucket/core/model/AccountWebHookEvent.scala
new file mode 100644
index 0000000..36ffa3c
--- /dev/null
+++ b/src/main/scala/gitbucket/core/model/AccountWebHookEvent.scala
@@ -0,0 +1,34 @@
+package gitbucket.core.model
+
+trait AccountWebHookEventComponent extends TemplateComponent {
+ self: Profile =>
+
+ import profile.api._
+ import gitbucket.core.model.Profile.AccountWebHooks
+
+ lazy val AccountWebHookEvents = TableQuery[AccountWebHookEvents]
+
+ class AccountWebHookEvents(tag: Tag) extends Table[AccountWebHookEvent](tag, "ACCOUNT_WEB_HOOK_EVENT") with BasicTemplate {
+ val url = column[String]("URL")
+ val event = column[WebHook.Event]("EVENT")
+
+ def * = (userName, url, event) <> ((AccountWebHookEvent.apply _).tupled, AccountWebHookEvent.unapply)
+
+ def byAccountWebHook(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind)
+
+ def byAccountWebHook(owner: Rep[String], url: Rep[String]) =
+ (this.userName === userName) && (this.url === url)
+
+ def byAccountWebHook(webhook: AccountWebHooks) =
+ (this.userName === webhook.userName) && (this.url === webhook.url)
+
+ def byPrimaryKey(userName: String, url: String, event: WebHook.Event) =
+ (this.userName === userName.bind) && (this.url === url.bind) && (this.event === event.bind)
+ }
+}
+
+case class AccountWebHookEvent(
+ userName: String,
+ url: String,
+ event: WebHook.Event
+ )
diff --git a/src/main/scala/gitbucket/core/model/BasicTemplate.scala b/src/main/scala/gitbucket/core/model/BasicTemplate.scala
index 2ebb28c..5608bbd 100644
--- a/src/main/scala/gitbucket/core/model/BasicTemplate.scala
+++ b/src/main/scala/gitbucket/core/model/BasicTemplate.scala
@@ -7,6 +7,10 @@
val userName = column[String]("USER_NAME")
val repositoryName = column[String]("REPOSITORY_NAME")
+ def byAccount(userName: String) = (this.userName === userName.bind)
+
+ def byAccount(userName: Rep[String]) = (this.userName === userName)
+
def byRepository(owner: String, repository: String) =
(userName === owner.bind) && (repositoryName === repository.bind)
diff --git a/src/main/scala/gitbucket/core/model/Profile.scala b/src/main/scala/gitbucket/core/model/Profile.scala
index 876188c..807456b 100644
--- a/src/main/scala/gitbucket/core/model/Profile.scala
+++ b/src/main/scala/gitbucket/core/model/Profile.scala
@@ -16,6 +16,11 @@
)
/**
+ * WebHookBase.Event Column Types
+ */
+ implicit val eventColumnType = MappedColumnType.base[WebHook.Event, String](_.name, WebHook.Event.valueOf(_))
+
+ /**
* Extends Column to add conditional condition
*/
implicit class RichColumn(c1: Rep[Boolean]){
@@ -52,8 +57,10 @@
with PullRequestComponent
with RepositoryComponent
with SshKeyComponent
- with WebHookComponent
- with WebHookEventComponent
+ with RepositoryWebHookComponent
+ with RepositoryWebHookEventComponent
+ with AccountWebHookComponent
+ with AccountWebHookEventComponent
with ProtectedBranchComponent
with DeployKeyComponent
diff --git a/src/main/scala/gitbucket/core/model/RepositoryWebHook.scala b/src/main/scala/gitbucket/core/model/RepositoryWebHook.scala
new file mode 100644
index 0000000..967d067
--- /dev/null
+++ b/src/main/scala/gitbucket/core/model/RepositoryWebHook.scala
@@ -0,0 +1,27 @@
+package gitbucket.core.model
+
+trait RepositoryWebHookComponent extends TemplateComponent { self: Profile =>
+ import profile.api._
+
+ implicit val whContentTypeColumnType = MappedColumnType.base[WebHookContentType, String](whct => whct.code , code => WebHookContentType.valueOf(code))
+
+ lazy val RepositoryWebHooks = TableQuery[RepositoryWebHooks]
+
+ class RepositoryWebHooks(tag: Tag) extends Table[RepositoryWebHook](tag, "WEB_HOOK") with BasicTemplate {
+ val url = column[String]("URL")
+ val token = column[Option[String]]("TOKEN")
+ val ctype = column[WebHookContentType]("CTYPE")
+ def * = (userName, repositoryName, url, ctype, token) <> ((RepositoryWebHook.apply _).tupled, RepositoryWebHook.unapply)
+
+ def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
+ }
+}
+
+
+case class RepositoryWebHook(
+ userName: String,
+ repositoryName: String,
+ url: String,
+ ctype: WebHookContentType,
+ token: Option[String]
+) extends WebHook
diff --git a/src/main/scala/gitbucket/core/model/RepositoryWebHookEvent.scala b/src/main/scala/gitbucket/core/model/RepositoryWebHookEvent.scala
new file mode 100644
index 0000000..83cbea5
--- /dev/null
+++ b/src/main/scala/gitbucket/core/model/RepositoryWebHookEvent.scala
@@ -0,0 +1,28 @@
+package gitbucket.core.model
+
+trait RepositoryWebHookEventComponent extends TemplateComponent { self: Profile =>
+ import profile.api._
+ import gitbucket.core.model.Profile.RepositoryWebHooks
+
+ lazy val RepositoryWebHookEvents = TableQuery[RepositoryWebHookEvents]
+
+ class RepositoryWebHookEvents(tag: Tag) extends Table[RepositoryWebHookEvent](tag, "WEB_HOOK_EVENT") with BasicTemplate {
+ val url = column[String]("URL")
+ val event = column[WebHook.Event]("EVENT")
+ def * = (userName, repositoryName, url, event) <> ((RepositoryWebHookEvent.apply _).tupled, RepositoryWebHookEvent.unapply)
+
+ def byRepositoryWebHook(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
+ def byRepositoryWebHook(owner: Rep[String], repository: Rep[String], url: Rep[String]) =
+ byRepository(userName, repositoryName) && (this.url === url)
+ def byRepositoryWebHook(webhook: RepositoryWebHooks) =
+ byRepository(webhook.userName, webhook.repositoryName) && (this.url === webhook.url)
+ def byPrimaryKey(owner: String, repository: String, url: String, event: WebHook.Event) = byRepositoryWebHook(owner, repository, url) && (this.event === event.bind)
+ }
+}
+
+case class RepositoryWebHookEvent(
+ userName: String,
+ repositoryName: String,
+ url: String,
+ event: WebHook.Event
+)
diff --git a/src/main/scala/gitbucket/core/model/WebHook.scala b/src/main/scala/gitbucket/core/model/WebHook.scala
index 48de21b..3643dfb 100644
--- a/src/main/scala/gitbucket/core/model/WebHook.scala
+++ b/src/main/scala/gitbucket/core/model/WebHook.scala
@@ -1,22 +1,5 @@
package gitbucket.core.model
-trait WebHookComponent extends TemplateComponent { self: Profile =>
- import profile.api._
-
- implicit val whContentTypeColumnType = MappedColumnType.base[WebHookContentType, String](whct => whct.code , code => WebHookContentType.valueOf(code))
-
- lazy val WebHooks = TableQuery[WebHooks]
-
- class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate {
- val url = column[String]("URL")
- val token = column[Option[String]]("TOKEN")
- val ctype = column[WebHookContentType]("CTYPE")
- def * = (userName, repositoryName, url, ctype, token) <> ((WebHook.apply _).tupled, WebHook.unapply)
-
- def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
- }
-}
-
abstract sealed case class WebHookContentType(code: String, ctype: String)
object WebHookContentType {
@@ -33,13 +16,11 @@
def valueOpt(code: String): Option[WebHookContentType] = map.get(code)
}
-case class WebHook(
- userName: String,
- repositoryName: String,
- url: String,
- ctype: WebHookContentType,
- token: Option[String]
-)
+trait WebHook{
+ val url: String
+ val ctype: WebHookContentType
+ val token: Option[String]
+}
object WebHook {
abstract sealed class Event(val name: String)
@@ -86,6 +67,7 @@
TeamAdd,
Watch
)
+
private val map: Map[String,Event] = values.map(e => e.name -> e).toMap
def valueOf(name: String): Event = map(name)
def valueOpt(name: String): Option[Event] = map.get(name)
diff --git a/src/main/scala/gitbucket/core/model/WebHookEvent.scala b/src/main/scala/gitbucket/core/model/WebHookEvent.scala
deleted file mode 100644
index d9f5a55..0000000
--- a/src/main/scala/gitbucket/core/model/WebHookEvent.scala
+++ /dev/null
@@ -1,30 +0,0 @@
-package gitbucket.core.model
-
-trait WebHookEventComponent extends TemplateComponent { self: Profile =>
- import profile.api._
- import gitbucket.core.model.Profile.WebHooks
-
- lazy val WebHookEvents = TableQuery[WebHookEvents]
-
- implicit val typedType = MappedColumnType.base[WebHook.Event, String](_.name, WebHook.Event.valueOf(_))
-
- class WebHookEvents(tag: Tag) extends Table[WebHookEvent](tag, "WEB_HOOK_EVENT") with BasicTemplate {
- val url = column[String]("URL")
- val event = column[WebHook.Event]("EVENT")
- def * = (userName, repositoryName, url, event) <> ((WebHookEvent.apply _).tupled, WebHookEvent.unapply)
-
- def byWebHook(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
- def byWebHook(owner: Rep[String], repository: Rep[String], url: Rep[String]) =
- byRepository(userName, repositoryName) && (this.url === url)
- def byWebHook(webhook: WebHooks) =
- byRepository(webhook.userName, webhook.repositoryName) && (this.url === webhook.url)
- def byPrimaryKey(owner: String, repository: String, url: String, event: WebHook.Event) = byWebHook(owner, repository, url) && (this.event === event.bind)
- }
-}
-
-case class WebHookEvent(
- userName: String,
- repositoryName: String,
- url: String,
- event: WebHook.Event
-)
diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala
index 9426507..a2fe53d 100644
--- a/src/main/scala/gitbucket/core/service/RepositoryService.scala
+++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala
@@ -59,8 +59,8 @@
(Repositories filter { t => t.byRepository(oldUserName, oldRepositoryName) } firstOption).map { repository =>
Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName)
- val webHooks = WebHooks .filter(_.byRepository(oldUserName, oldRepositoryName)).list
- val webHookEvents = WebHookEvents .filter(_.byRepository(oldUserName, oldRepositoryName)).list
+ val webHooks = RepositoryWebHooks .filter(_.byRepository(oldUserName, oldRepositoryName)).list
+ val webHookEvents = RepositoryWebHookEvents .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val milestones = Milestones .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val issueId = IssueId .filter(_.byRepository(oldUserName, oldRepositoryName)).list
val issues = Issues .filter(_.byRepository(oldUserName, oldRepositoryName)).list
@@ -93,8 +93,8 @@
deleteRepository(oldUserName, oldRepositoryName)
- WebHooks .insertAll(webHooks .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
- WebHookEvents.insertAll(webHookEvents .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
+ RepositoryWebHooks .insertAll(webHooks .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
+ RepositoryWebHookEvents.insertAll(webHookEvents .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
Milestones .insertAll(milestones .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
Priorities .insertAll(priorities .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
IssueId .insertAll(issueId .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*)
@@ -170,8 +170,8 @@
Priorities .filter(_.byRepository(userName, repositoryName)).delete
IssueId .filter(_.byRepository(userName, repositoryName)).delete
Milestones .filter(_.byRepository(userName, repositoryName)).delete
- WebHooks .filter(_.byRepository(userName, repositoryName)).delete
- WebHookEvents .filter(_.byRepository(userName, repositoryName)).delete
+ RepositoryWebHooks .filter(_.byRepository(userName, repositoryName)).delete
+ RepositoryWebHookEvents .filter(_.byRepository(userName, repositoryName)).delete
DeployKeys .filter(_.byRepository(userName, repositoryName)).delete
Repositories .filter(_.byRepository(userName, repositoryName)).delete
diff --git a/src/main/scala/gitbucket/core/service/WebHookService.scala b/src/main/scala/gitbucket/core/service/WebHookService.scala
index 35fe847..e6bb4c1 100644
--- a/src/main/scala/gitbucket/core/service/WebHookService.scala
+++ b/src/main/scala/gitbucket/core/service/WebHookService.scala
@@ -3,7 +3,7 @@
import fr.brouillard.oss.security.xhub.XHub
import fr.brouillard.oss.security.xhub.XHub.{XHubConverter, XHubDigest}
import gitbucket.core.api._
-import gitbucket.core.model.{Account, CommitComment, Issue, IssueComment, PullRequest, WebHook, WebHookEvent}
+import gitbucket.core.model.{Account, CommitComment, Issue, IssueComment, PullRequest, WebHook, RepositoryWebHook, RepositoryWebHookEvent, AccountWebHook, AccountWebHookEvent}
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._
import org.apache.http.client.utils.URLEncodedUtils
@@ -32,45 +32,86 @@
private val logger = LoggerFactory.getLogger(classOf[WebHookService])
/** get All WebHook informations of repository */
- def getWebHooks(owner: String, repository: String)(implicit s: Session): List[(WebHook, Set[WebHook.Event])] =
- WebHooks.filter(_.byRepository(owner, repository))
- .join(WebHookEvents).on { (w, t) => t.byWebHook(w) }
+ def getWebHooks(owner: String, repository: String)(implicit s: Session): List[(RepositoryWebHook, Set[WebHook.Event])] =
+ RepositoryWebHooks.filter(_.byRepository(owner, repository))
+ .join(RepositoryWebHookEvents).on { (w, t) => t.byRepositoryWebHook(w) }
.map { case (w, t) => w -> t.event }
.list.groupBy(_._1).mapValues(_.map(_._2).toSet).toList.sortBy(_._1.url)
/** get All WebHook informations of repository event */
- def getWebHooksByEvent(owner: String, repository: String, event: WebHook.Event)(implicit s: Session): List[WebHook] =
- WebHooks.filter(_.byRepository(owner, repository))
- .join(WebHookEvents).on { (wh, whe) => whe.byWebHook(wh) }
- .filter { case (wh, whe) => whe.event === event.bind }
+ def getWebHooksByEvent(owner: String, repository: String, event: WebHook.Event)(implicit s: Session): List[RepositoryWebHook] =
+ RepositoryWebHooks.filter(_.byRepository(owner, repository))
+ .join(RepositoryWebHookEvents).on { (wh, whe) => whe.byRepositoryWebHook(wh) }
+ .filter { case (wh, whe) => whe.event === event.bind}
.map{ case (wh, whe) => wh }
.list.distinct
/** get All WebHook information from repository to url */
- def getWebHook(owner: String, repository: String, url: String)(implicit s: Session): Option[(WebHook, Set[WebHook.Event])] =
- WebHooks
+ def getWebHook(owner: String, repository: String, url: String)(implicit s: Session): Option[(RepositoryWebHook, Set[WebHook.Event])] =
+ RepositoryWebHooks
.filter(_.byPrimaryKey(owner, repository, url))
- .join(WebHookEvents).on { (w, t) => t.byWebHook(w) }
+ .join(RepositoryWebHookEvents).on { (w, t) => t.byRepositoryWebHook(w) }
.map { case (w, t) => w -> t.event }
.list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption
def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = {
- WebHooks insert WebHook(owner, repository, url, ctype, token)
+ RepositoryWebHooks insert RepositoryWebHook(owner, repository, url, ctype, token)
events.map { event: WebHook.Event =>
- WebHookEvents insert WebHookEvent(owner, repository, url, event)
+ RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
}
}
def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = {
- WebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => (w.ctype, w.token)).update((ctype, token))
- WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete
+ RepositoryWebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => (w.ctype, w.token)).update((ctype, token))
+ RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete
events.map { event: WebHook.Event =>
- WebHookEvents insert WebHookEvent(owner, repository, url, event)
+ RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
}
}
def deleteWebHook(owner: String, repository: String, url :String)(implicit s: Session): Unit =
- WebHooks.filter(_.byPrimaryKey(owner, repository, url)).delete
+ RepositoryWebHooks.filter(_.byPrimaryKey(owner, repository, url)).delete
+
+ /** get All AccountWebHook informations of user */
+ def getAccountWebHooks(owner: String)(implicit s: Session): List[(AccountWebHook, Set[WebHook.Event])] =
+ AccountWebHooks.filter(_.byAccount(owner))
+ .join(AccountWebHookEvents).on { (w, t) => t.byAccountWebHook(w) }
+ .map { case (w, t) => w -> t.event }
+ .list.groupBy(_._1).mapValues(_.map(_._2).toSet).toList.sortBy(_._1.url)
+
+ /** get All AccountWebHook informations of repository event */
+ def getAccountWebHooksByEvent(owner: String, event: WebHook.Event)(implicit s: Session): List[AccountWebHook] =
+ AccountWebHooks.filter(_.byAccount(owner))
+ .join(AccountWebHookEvents).on { (wh, whe) => whe.byAccountWebHook(wh) }
+ .filter { case (wh, whe) => whe.event === event.bind}
+ .map{ case (wh, whe) => wh }
+ .list.distinct
+
+ /** get All AccountWebHook information from repository to url */
+ def getAccountWebHook(owner: String, url: String)(implicit s: Session): Option[(AccountWebHook, Set[WebHook.Event])] =
+ AccountWebHooks
+ .filter(_.byPrimaryKey(owner, url))
+ .join(AccountWebHookEvents).on { (w, t) => t.byAccountWebHook(w) }
+ .map { case (w, t) => w -> t.event }
+ .list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption
+
+ def addAccountWebHook(owner: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = {
+ AccountWebHooks insert AccountWebHook(owner, url, ctype, token)
+ events.map { event: WebHook.Event =>
+ AccountWebHookEvents insert AccountWebHookEvent(owner, url, event)
+ }
+ }
+
+ def updateAccountWebHook(owner: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = {
+ AccountWebHooks.filter(_.byPrimaryKey(owner, url)).map(w => (w.ctype, w.token)).update((ctype, token))
+ AccountWebHookEvents.filter(_.byAccountWebHook(owner, url)).delete
+ events.map { event: WebHook.Event =>
+ AccountWebHookEvents insert AccountWebHookEvent(owner, url, event)
+ }
+ }
+
+ def deleteAccountWebHook(owner: String, url :String)(implicit s: Session): Unit =
+ AccountWebHooks.filter(_.byPrimaryKey(owner, url)).delete
def callWebHookOf(owner: String, repository: String, event: WebHook.Event)(makePayload: => Option[WebHookPayload])
(implicit s: Session, c: JsonFormat.Context): Unit = {
@@ -78,6 +119,10 @@
if(webHooks.nonEmpty){
makePayload.map(callWebHook(event, webHooks, _))
}
+ val accountWebHooks = getAccountWebHooksByEvent(owner, event)
+ if(accountWebHooks.nonEmpty){
+ makePayload.map(callWebHook(event, accountWebHooks, _))
+ }
}
def callWebHook(event: WebHook.Event, webHooks: List[WebHook], payload: WebHookPayload)
@@ -207,7 +252,7 @@
/** @return Map[(issue, issueUser, pullRequest, baseOwner, headOwner), webHooks] */
def getPullRequestsByRequestForWebhook(userName:String, repositoryName:String, branch:String)
- (implicit s: Session): Map[(Issue, Account, PullRequest, Account, Account), List[WebHook]] =
+ (implicit s: Session): Map[(Issue, Account, PullRequest, Account, Account), List[RepositoryWebHook]] =
(for{
is <- Issues if is.closed === false.bind
pr <- PullRequests if pr.byPrimaryKey(is.userName, is.repositoryName, is.issueId)
@@ -217,8 +262,8 @@
bu <- Accounts if bu.userName === pr.userName
ru <- Accounts if ru.userName === pr.requestUserName
iu <- Accounts if iu.userName === is.openedUserName
- wh <- WebHooks if wh.byRepository(is.userName , is.repositoryName)
- wht <- WebHookEvents if wht.event === WebHook.PullRequest.asInstanceOf[WebHook.Event].bind && wht.byWebHook(wh)
+ wh <- RepositoryWebHooks if wh.byRepository(is.userName , is.repositoryName)
+ wht <- RepositoryWebHookEvents if wht.event === WebHook.PullRequest.asInstanceOf[WebHook.Event].bind && wht.byRepositoryWebHook(wh)
} yield {
((is, iu, pr, bu, ru), wh)
}).list.groupBy(_._1).mapValues(_.map(_._2))
@@ -345,6 +390,17 @@
repositoryInfo,
owner= ApiUser(repositoryOwner))
)
+
+ def createDummyPayload(sender: Account): WebHookPushPayload =
+ WebHookPushPayload(
+ pusher = ApiPusher(sender),
+ sender = ApiUser(sender),
+ ref = "refs/heads/master",
+ before = "adc83b19e793491b1c6ea0fd8b46cd9f32e592fc",
+ after = "adc83b19e793491b1c6ea0fd8b46cd9f32e592fc",
+ commits = List.empty,
+ repository = ApiRepository.forDummyPayload(ApiUser(sender))
+ )
}
// https://developer.github.com/v3/activity/events/types/#issuesevent
diff --git a/src/main/twirl/gitbucket/core/account/edithooks.scala.html b/src/main/twirl/gitbucket/core/account/edithooks.scala.html
new file mode 100644
index 0000000..4b33246
--- /dev/null
+++ b/src/main/twirl/gitbucket/core/account/edithooks.scala.html
@@ -0,0 +1,191 @@
+@(webHook: gitbucket.core.model.AccountWebHook,
+events: Set[gitbucket.core.model.WebHook.Event],
+account: gitbucket.core.model.Account,
+groupNames: List[String],
+info: Option[Any],
+create: Boolean)(implicit context: gitbucket.core.controller.Context)
+@import gitbucket.core.view.helpers
+@import gitbucket.core.model.WebHook._
+@import gitbucket.core.model.WebHookContentType
+@check(name: String, event: Event) = {
+name="@(name).@event.name" value="on" @if(events(event)){checked}
+}
+@gitbucket.core.account.html.main(account, groupNames, "webhooks"){
+
+
Webhook / Manage webhook
+
+
+
+
+
+
+
+
request to
+
+
+
+
+
+ ERROR
+
+
+ Headers
+
+ Payload
+
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/src/main/twirl/gitbucket/core/account/main.scala.html b/src/main/twirl/gitbucket/core/account/main.scala.html
index 4cdd5e5..747db11 100644
--- a/src/main/twirl/gitbucket/core/account/main.scala.html
+++ b/src/main/twirl/gitbucket/core/account/main.scala.html
@@ -43,6 +43,7 @@
} else {
Public activity
}
+ Webhooks
@gitbucket.core.plugin.PluginRegistry().getProfileTabs.map { tab =>
@tab(account, context).map { link =>
@link.label
diff --git a/src/main/twirl/gitbucket/core/account/webhook.scala.html b/src/main/twirl/gitbucket/core/account/webhook.scala.html
new file mode 100644
index 0000000..2d08c57
--- /dev/null
+++ b/src/main/twirl/gitbucket/core/account/webhook.scala.html
@@ -0,0 +1,39 @@
+@(account: gitbucket.core.model.Account,
+groupNames: List[String],
+webHooks: List[(gitbucket.core.model.AccountWebHook, Set[gitbucket.core.model.WebHook.Event])])(implicit context: gitbucket.core.controller.Context)
+@import gitbucket.core.view.helpers
+@gitbucket.core.account.html.main(account, groupNames, "webhooks"){
+
+
+ Webhooks
+
+
+
+ Webhooks allow external services to be notified when certain events happen within your repository.
+ When the specified events happen, we’ll send a POST request to each of the URLs you provide.
+ Learn more in GitBucket Wiki Webhook Page.
+
+
Add webhook
+
+
+ @webHooks.map { case (webHook, events) =>
+
+
+ @webHook.url
+
+ (@events.map(_.name).mkString(", "))
+ |
+
+ |
+ }
+
+
+
+}
diff --git a/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala b/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala
index 238cafb..f3c0535 100644
--- a/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala
+++ b/src/test/scala/gitbucket/core/service/WebHookServiceSpec.scala
@@ -1,6 +1,6 @@
package gitbucket.core.service
-import gitbucket.core.model.WebHook
+import gitbucket.core.model.{WebHook, RepositoryWebHook}
import org.scalatest.FunSuite
import gitbucket.core.model.WebHookContentType
@@ -47,17 +47,17 @@
val formType = WebHookContentType.FORM
val jsonType = WebHookContentType.JSON
service.addWebHook("user1", "repo1", "http://example.com", Set(WebHook.PullRequest), formType, Some("key"))
- assert(service.getWebHooks("user1", "repo1") == List((WebHook("user1","repo1","http://example.com", formType, Some("key")),Set(WebHook.PullRequest))))
- assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((WebHook("user1","repo1","http://example.com", formType, Some("key")),Set(WebHook.PullRequest))))
- assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == List((WebHook("user1","repo1","http://example.com", formType, Some("key")))))
+ assert(service.getWebHooks("user1", "repo1") == List((RepositoryWebHook("user1","repo1","http://example.com", formType, Some("key")),Set(WebHook.PullRequest))))
+ assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((RepositoryWebHook("user1","repo1","http://example.com", formType, Some("key")),Set(WebHook.PullRequest))))
+ assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == List((RepositoryWebHook("user1","repo1","http://example.com", formType, Some("key")))))
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.Push) == Nil)
assert(service.getWebHook("user1", "repo1", "http://example.com2") == None)
assert(service.getWebHook("user2", "repo1", "http://example.com") == None)
assert(service.getWebHook("user1", "repo2", "http://example.com") == None)
service.updateWebHook("user1", "repo1", "http://example.com", Set(WebHook.Push, WebHook.Issues), jsonType, Some("key"))
- assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((WebHook("user1","repo1","http://example.com", jsonType, Some("key")),Set(WebHook.Push, WebHook.Issues))))
+ assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((RepositoryWebHook("user1","repo1","http://example.com", jsonType, Some("key")),Set(WebHook.Push, WebHook.Issues))))
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == Nil)
- assert(service.getWebHooksByEvent("user1", "repo1", WebHook.Push) == List((WebHook("user1","repo1","http://example.com", jsonType, Some("key")))))
+ assert(service.getWebHooksByEvent("user1", "repo1", WebHook.Push) == List((RepositoryWebHook("user1","repo1","http://example.com", jsonType, Some("key")))))
service.deleteWebHook("user1", "repo1", "http://example.com")
assert(service.getWebHook("user1", "repo1", "http://example.com") == None)
} }
@@ -69,11 +69,11 @@
service.addWebHook("user1", "repo1", "http://example.com/2", Set(WebHook.Push), ctype, Some("key"))
service.addWebHook("user1", "repo1", "http://example.com/3", Set(WebHook.PullRequest,WebHook.Push), ctype, Some("key"))
assert(service.getWebHooks("user1", "repo1") == List(
- WebHook("user1","repo1","http://example.com/1", ctype, Some("key"))->Set(WebHook.PullRequest),
- WebHook("user1","repo1","http://example.com/2", ctype, Some("key"))->Set(WebHook.Push),
- WebHook("user1","repo1","http://example.com/3", ctype, Some("key"))->Set(WebHook.PullRequest,WebHook.Push)))
+ RepositoryWebHook("user1","repo1","http://example.com/1", ctype, Some("key"))->Set(WebHook.PullRequest),
+ RepositoryWebHook("user1","repo1","http://example.com/2", ctype, Some("key"))->Set(WebHook.Push),
+ RepositoryWebHook("user1","repo1","http://example.com/3", ctype, Some("key"))->Set(WebHook.PullRequest,WebHook.Push)))
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == List(
- WebHook("user1","repo1","http://example.com/1", ctype, Some("key")),
- WebHook("user1","repo1","http://example.com/3", ctype, Some("key"))))
+ RepositoryWebHook("user1","repo1","http://example.com/1", ctype, Some("key")),
+ RepositoryWebHook("user1","repo1","http://example.com/3", ctype, Some("key"))))
} }
-}
\ No newline at end of file
+}