mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-06 05:25:50 +01:00
Fix CRLF to LF
This commit is contained in:
@@ -1,86 +1,86 @@
|
||||
package app
|
||||
|
||||
import util._
|
||||
import util.Implicits._
|
||||
import service._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
class IndexController extends IndexControllerBase
|
||||
with RepositoryService with ActivityService with AccountService with UsersAuthenticator
|
||||
|
||||
trait IndexControllerBase extends ControllerBase {
|
||||
self: RepositoryService with ActivityService with AccountService with UsersAuthenticator =>
|
||||
|
||||
case class SignInForm(userName: String, password: String)
|
||||
|
||||
val form = mapping(
|
||||
"userName" -> trim(label("Username", text(required))),
|
||||
"password" -> trim(label("Password", text(required)))
|
||||
)(SignInForm.apply)
|
||||
|
||||
get("/"){
|
||||
val loginAccount = context.loginAccount
|
||||
|
||||
html.index(getRecentActivities(),
|
||||
getVisibleRepositories(loginAccount, context.baseUrl),
|
||||
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl) }.getOrElse(Nil)
|
||||
)
|
||||
}
|
||||
|
||||
get("/signin"){
|
||||
val redirect = params.get("redirect")
|
||||
if(redirect.isDefined && redirect.get.startsWith("/")){
|
||||
flash += Keys.Flash.Redirect -> redirect.get
|
||||
}
|
||||
html.signin()
|
||||
}
|
||||
|
||||
post("/signin", form){ form =>
|
||||
authenticate(context.settings, form.userName, form.password) match {
|
||||
case Some(account) => signin(account)
|
||||
case None => redirect("/signin")
|
||||
}
|
||||
}
|
||||
|
||||
get("/signout"){
|
||||
session.invalidate
|
||||
redirect("/")
|
||||
}
|
||||
|
||||
get("/activities.atom"){
|
||||
contentType = "application/atom+xml; type=feed"
|
||||
helper.xml.feed(getRecentActivities())
|
||||
}
|
||||
|
||||
/**
|
||||
* Set account information into HttpSession and redirect.
|
||||
*/
|
||||
private def signin(account: model.Account) = {
|
||||
session.setAttribute(Keys.Session.LoginAccount, account)
|
||||
updateLastLoginDate(account.userName)
|
||||
|
||||
flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl =>
|
||||
if(redirectUrl.stripSuffix("/") == request.getContextPath){
|
||||
redirect("/")
|
||||
} else {
|
||||
redirect(redirectUrl)
|
||||
}
|
||||
}.getOrElse {
|
||||
redirect("/")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON API for collaborator completion.
|
||||
*
|
||||
* TODO Move to other controller?
|
||||
*/
|
||||
get("/_user/proposals")(usersOnly {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("options" -> getAllUsers().filter(!_.isGroupAccount).map(_.userName).toArray)
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
package app
|
||||
|
||||
import util._
|
||||
import util.Implicits._
|
||||
import service._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
class IndexController extends IndexControllerBase
|
||||
with RepositoryService with ActivityService with AccountService with UsersAuthenticator
|
||||
|
||||
trait IndexControllerBase extends ControllerBase {
|
||||
self: RepositoryService with ActivityService with AccountService with UsersAuthenticator =>
|
||||
|
||||
case class SignInForm(userName: String, password: String)
|
||||
|
||||
val form = mapping(
|
||||
"userName" -> trim(label("Username", text(required))),
|
||||
"password" -> trim(label("Password", text(required)))
|
||||
)(SignInForm.apply)
|
||||
|
||||
get("/"){
|
||||
val loginAccount = context.loginAccount
|
||||
|
||||
html.index(getRecentActivities(),
|
||||
getVisibleRepositories(loginAccount, context.baseUrl),
|
||||
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl) }.getOrElse(Nil)
|
||||
)
|
||||
}
|
||||
|
||||
get("/signin"){
|
||||
val redirect = params.get("redirect")
|
||||
if(redirect.isDefined && redirect.get.startsWith("/")){
|
||||
flash += Keys.Flash.Redirect -> redirect.get
|
||||
}
|
||||
html.signin()
|
||||
}
|
||||
|
||||
post("/signin", form){ form =>
|
||||
authenticate(context.settings, form.userName, form.password) match {
|
||||
case Some(account) => signin(account)
|
||||
case None => redirect("/signin")
|
||||
}
|
||||
}
|
||||
|
||||
get("/signout"){
|
||||
session.invalidate
|
||||
redirect("/")
|
||||
}
|
||||
|
||||
get("/activities.atom"){
|
||||
contentType = "application/atom+xml; type=feed"
|
||||
helper.xml.feed(getRecentActivities())
|
||||
}
|
||||
|
||||
/**
|
||||
* Set account information into HttpSession and redirect.
|
||||
*/
|
||||
private def signin(account: model.Account) = {
|
||||
session.setAttribute(Keys.Session.LoginAccount, account)
|
||||
updateLastLoginDate(account.userName)
|
||||
|
||||
flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl =>
|
||||
if(redirectUrl.stripSuffix("/") == request.getContextPath){
|
||||
redirect("/")
|
||||
} else {
|
||||
redirect(redirectUrl)
|
||||
}
|
||||
}.getOrElse {
|
||||
redirect("/")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON API for collaborator completion.
|
||||
*
|
||||
* TODO Move to other controller?
|
||||
*/
|
||||
get("/_user/proposals")(usersOnly {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("options" -> getAllUsers().filter(!_.isGroupAccount).map(_.userName).toArray)
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,267 +1,267 @@
|
||||
package app
|
||||
|
||||
import service._
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import util.Implicits._
|
||||
import util.{UsersAuthenticator, OwnerAuthenticator}
|
||||
import util.JGitUtil.CommitInfo
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.scalatra.i18n.Messages
|
||||
import service.WebHookService.WebHookPayload
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
class RepositorySettingsController extends RepositorySettingsControllerBase
|
||||
with RepositoryService with AccountService with WebHookService
|
||||
with OwnerAuthenticator with UsersAuthenticator
|
||||
|
||||
trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with WebHookService
|
||||
with OwnerAuthenticator with UsersAuthenticator =>
|
||||
|
||||
// for repository options
|
||||
case class OptionsForm(repositoryName: String, description: Option[String], defaultBranch: String, isPrivate: Boolean)
|
||||
|
||||
val optionsForm = mapping(
|
||||
"repositoryName" -> trim(label("Description" , text(required, maxlength(40), identifier, renameRepositoryName))),
|
||||
"description" -> trim(label("Description" , optional(text()))),
|
||||
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
|
||||
"isPrivate" -> trim(label("Repository Type", boolean()))
|
||||
)(OptionsForm.apply)
|
||||
|
||||
// for collaborator addition
|
||||
case class CollaboratorForm(userName: String)
|
||||
|
||||
val collaboratorForm = mapping(
|
||||
"userName" -> trim(label("Username", text(required, collaborator)))
|
||||
)(CollaboratorForm.apply)
|
||||
|
||||
// for web hook url addition
|
||||
case class WebHookForm(url: String)
|
||||
|
||||
val webHookForm = mapping(
|
||||
"url" -> trim(label("url", text(required, webHook)))
|
||||
)(WebHookForm.apply)
|
||||
|
||||
// for transfer ownership
|
||||
case class TransferOwnerShipForm(newOwner: String)
|
||||
|
||||
val transferForm = mapping(
|
||||
"newOwner" -> trim(label("New owner", text(required, transferUser)))
|
||||
)(TransferOwnerShipForm.apply)
|
||||
|
||||
/**
|
||||
* Redirect to the Options page.
|
||||
*/
|
||||
get("/:owner/:repository/settings")(ownerOnly { repository =>
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/options")
|
||||
})
|
||||
|
||||
/**
|
||||
* Display the Options page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/options")(ownerOnly {
|
||||
settings.html.options(_, flash.get("info"))
|
||||
})
|
||||
|
||||
/**
|
||||
* Save the repository options.
|
||||
*/
|
||||
post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
|
||||
saveRepositoryOptions(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
form.description,
|
||||
if(repository.branchList.isEmpty) "master" else form.defaultBranch,
|
||||
repository.repository.parentUserName.map { _ =>
|
||||
repository.repository.isPrivate
|
||||
} getOrElse form.isPrivate
|
||||
)
|
||||
// Change repository name
|
||||
if(repository.name != form.repositoryName){
|
||||
// Update database
|
||||
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
|
||||
// Move git repository
|
||||
defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
|
||||
FileUtils.moveDirectory(dir, getRepositoryDir(repository.owner, form.repositoryName))
|
||||
}
|
||||
// Move wiki repository
|
||||
defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
|
||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName))
|
||||
}
|
||||
}
|
||||
flash += "info" -> "Repository settings has been updated."
|
||||
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
|
||||
})
|
||||
|
||||
/**
|
||||
* Display the Collaborators page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
|
||||
settings.html.collaborators(
|
||||
getCollaborators(repository.owner, repository.name),
|
||||
getAccountByUserName(repository.owner).get.isGroupAccount,
|
||||
repository)
|
||||
})
|
||||
|
||||
/**
|
||||
* Add the collaborator.
|
||||
*/
|
||||
post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) =>
|
||||
if(!getAccountByUserName(repository.owner).get.isGroupAccount){
|
||||
addCollaborator(repository.owner, repository.name, form.userName)
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
|
||||
})
|
||||
|
||||
/**
|
||||
* Add the collaborator.
|
||||
*/
|
||||
get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository =>
|
||||
if(!getAccountByUserName(repository.owner).get.isGroupAccount){
|
||||
removeCollaborator(repository.owner, repository.name, params("name"))
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
|
||||
})
|
||||
|
||||
/**
|
||||
* Display the web hook page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
|
||||
settings.html.hooks(getWebHookURLs(repository.owner, repository.name), repository, flash.get("info"))
|
||||
})
|
||||
|
||||
/**
|
||||
* Add the web hook URL.
|
||||
*/
|
||||
post("/:owner/:repository/settings/hooks/add", webHookForm)(ownerOnly { (form, repository) =>
|
||||
addWebHookURL(repository.owner, repository.name, form.url)
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||
})
|
||||
|
||||
/**
|
||||
* Delete the web hook URL.
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository =>
|
||||
deleteWebHookURL(repository.owner, repository.name, params("url"))
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||
})
|
||||
|
||||
/**
|
||||
* Send the test request to registered web hook URLs.
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
import scala.collection.JavaConverters._
|
||||
val commits = git.log
|
||||
.add(git.getRepository.resolve(repository.repository.defaultBranch))
|
||||
.setMaxCount(3)
|
||||
.call.iterator.asScala.map(new CommitInfo(_))
|
||||
|
||||
getWebHookURLs(repository.owner, repository.name) match {
|
||||
case webHookURLs if(webHookURLs.nonEmpty) =>
|
||||
for(ownerAccount <- getAccountByUserName(repository.owner)){
|
||||
callWebHook(repository.owner, repository.name, webHookURLs,
|
||||
WebHookPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount))
|
||||
}
|
||||
case _ =>
|
||||
}
|
||||
|
||||
flash += "info" -> "Test payload deployed!"
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||
})
|
||||
|
||||
/**
|
||||
* Display the danger zone.
|
||||
*/
|
||||
get("/:owner/:repository/settings/danger")(ownerOnly {
|
||||
settings.html.danger(_)
|
||||
})
|
||||
|
||||
/**
|
||||
* Transfer repository ownership.
|
||||
*/
|
||||
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
|
||||
// Change repository owner
|
||||
if(repository.owner != form.newOwner){
|
||||
// Update database
|
||||
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
|
||||
// Move git repository
|
||||
defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
|
||||
FileUtils.moveDirectory(dir, getRepositoryDir(form.newOwner, repository.name))
|
||||
}
|
||||
// Move wiki repository
|
||||
defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
|
||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(form.newOwner, repository.name))
|
||||
}
|
||||
}
|
||||
redirect(s"/${form.newOwner}/${repository.name}")
|
||||
})
|
||||
|
||||
/**
|
||||
* Delete the repository.
|
||||
*/
|
||||
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
|
||||
deleteRepository(repository.owner, repository.name)
|
||||
|
||||
FileUtils.deleteDirectory(getRepositoryDir(repository.owner, repository.name))
|
||||
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name))
|
||||
FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name))
|
||||
|
||||
redirect(s"/${repository.owner}")
|
||||
})
|
||||
|
||||
/**
|
||||
* Provides duplication check for web hook url.
|
||||
*/
|
||||
private def webHook: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
getWebHookURLs(params("owner"), params("repository")).map(_.url).find(_ == value).map(_ => "URL had been registered already.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides Constraint to validate the collaborator name.
|
||||
*/
|
||||
private def collaborator: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
getAccountByUserName(value) match {
|
||||
case None => Some("User does not exist.")
|
||||
case Some(x) if(x.isGroupAccount)
|
||||
=> Some("User does not exist.")
|
||||
case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
|
||||
=> Some("User can access this repository already.")
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate check for the rename repository name.
|
||||
*/
|
||||
private def renameRepositoryName: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
|
||||
params.get("repository").filter(_ != value).flatMap { _ =>
|
||||
params.get("owner").flatMap { userName =>
|
||||
getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides Constraint to validate the repository transfer user.
|
||||
*/
|
||||
private def transferUser: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
getAccountByUserName(value) match {
|
||||
case None => Some("User does not exist.")
|
||||
case Some(x) => if(x.userName == params("owner")){
|
||||
Some("This is current repository owner.")
|
||||
} else {
|
||||
params.get("repository").flatMap { repositoryName =>
|
||||
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map{ _ => "User already has same repository." }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
package app
|
||||
|
||||
import service._
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import util.Implicits._
|
||||
import util.{UsersAuthenticator, OwnerAuthenticator}
|
||||
import util.JGitUtil.CommitInfo
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.scalatra.i18n.Messages
|
||||
import service.WebHookService.WebHookPayload
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
class RepositorySettingsController extends RepositorySettingsControllerBase
|
||||
with RepositoryService with AccountService with WebHookService
|
||||
with OwnerAuthenticator with UsersAuthenticator
|
||||
|
||||
trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with WebHookService
|
||||
with OwnerAuthenticator with UsersAuthenticator =>
|
||||
|
||||
// for repository options
|
||||
case class OptionsForm(repositoryName: String, description: Option[String], defaultBranch: String, isPrivate: Boolean)
|
||||
|
||||
val optionsForm = mapping(
|
||||
"repositoryName" -> trim(label("Description" , text(required, maxlength(40), identifier, renameRepositoryName))),
|
||||
"description" -> trim(label("Description" , optional(text()))),
|
||||
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
|
||||
"isPrivate" -> trim(label("Repository Type", boolean()))
|
||||
)(OptionsForm.apply)
|
||||
|
||||
// for collaborator addition
|
||||
case class CollaboratorForm(userName: String)
|
||||
|
||||
val collaboratorForm = mapping(
|
||||
"userName" -> trim(label("Username", text(required, collaborator)))
|
||||
)(CollaboratorForm.apply)
|
||||
|
||||
// for web hook url addition
|
||||
case class WebHookForm(url: String)
|
||||
|
||||
val webHookForm = mapping(
|
||||
"url" -> trim(label("url", text(required, webHook)))
|
||||
)(WebHookForm.apply)
|
||||
|
||||
// for transfer ownership
|
||||
case class TransferOwnerShipForm(newOwner: String)
|
||||
|
||||
val transferForm = mapping(
|
||||
"newOwner" -> trim(label("New owner", text(required, transferUser)))
|
||||
)(TransferOwnerShipForm.apply)
|
||||
|
||||
/**
|
||||
* Redirect to the Options page.
|
||||
*/
|
||||
get("/:owner/:repository/settings")(ownerOnly { repository =>
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/options")
|
||||
})
|
||||
|
||||
/**
|
||||
* Display the Options page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/options")(ownerOnly {
|
||||
settings.html.options(_, flash.get("info"))
|
||||
})
|
||||
|
||||
/**
|
||||
* Save the repository options.
|
||||
*/
|
||||
post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
|
||||
saveRepositoryOptions(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
form.description,
|
||||
if(repository.branchList.isEmpty) "master" else form.defaultBranch,
|
||||
repository.repository.parentUserName.map { _ =>
|
||||
repository.repository.isPrivate
|
||||
} getOrElse form.isPrivate
|
||||
)
|
||||
// Change repository name
|
||||
if(repository.name != form.repositoryName){
|
||||
// Update database
|
||||
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
|
||||
// Move git repository
|
||||
defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
|
||||
FileUtils.moveDirectory(dir, getRepositoryDir(repository.owner, form.repositoryName))
|
||||
}
|
||||
// Move wiki repository
|
||||
defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
|
||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName))
|
||||
}
|
||||
}
|
||||
flash += "info" -> "Repository settings has been updated."
|
||||
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
|
||||
})
|
||||
|
||||
/**
|
||||
* Display the Collaborators page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
|
||||
settings.html.collaborators(
|
||||
getCollaborators(repository.owner, repository.name),
|
||||
getAccountByUserName(repository.owner).get.isGroupAccount,
|
||||
repository)
|
||||
})
|
||||
|
||||
/**
|
||||
* Add the collaborator.
|
||||
*/
|
||||
post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) =>
|
||||
if(!getAccountByUserName(repository.owner).get.isGroupAccount){
|
||||
addCollaborator(repository.owner, repository.name, form.userName)
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
|
||||
})
|
||||
|
||||
/**
|
||||
* Add the collaborator.
|
||||
*/
|
||||
get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository =>
|
||||
if(!getAccountByUserName(repository.owner).get.isGroupAccount){
|
||||
removeCollaborator(repository.owner, repository.name, params("name"))
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
|
||||
})
|
||||
|
||||
/**
|
||||
* Display the web hook page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
|
||||
settings.html.hooks(getWebHookURLs(repository.owner, repository.name), repository, flash.get("info"))
|
||||
})
|
||||
|
||||
/**
|
||||
* Add the web hook URL.
|
||||
*/
|
||||
post("/:owner/:repository/settings/hooks/add", webHookForm)(ownerOnly { (form, repository) =>
|
||||
addWebHookURL(repository.owner, repository.name, form.url)
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||
})
|
||||
|
||||
/**
|
||||
* Delete the web hook URL.
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository =>
|
||||
deleteWebHookURL(repository.owner, repository.name, params("url"))
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||
})
|
||||
|
||||
/**
|
||||
* Send the test request to registered web hook URLs.
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
import scala.collection.JavaConverters._
|
||||
val commits = git.log
|
||||
.add(git.getRepository.resolve(repository.repository.defaultBranch))
|
||||
.setMaxCount(3)
|
||||
.call.iterator.asScala.map(new CommitInfo(_))
|
||||
|
||||
getWebHookURLs(repository.owner, repository.name) match {
|
||||
case webHookURLs if(webHookURLs.nonEmpty) =>
|
||||
for(ownerAccount <- getAccountByUserName(repository.owner)){
|
||||
callWebHook(repository.owner, repository.name, webHookURLs,
|
||||
WebHookPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount))
|
||||
}
|
||||
case _ =>
|
||||
}
|
||||
|
||||
flash += "info" -> "Test payload deployed!"
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||
})
|
||||
|
||||
/**
|
||||
* Display the danger zone.
|
||||
*/
|
||||
get("/:owner/:repository/settings/danger")(ownerOnly {
|
||||
settings.html.danger(_)
|
||||
})
|
||||
|
||||
/**
|
||||
* Transfer repository ownership.
|
||||
*/
|
||||
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
|
||||
// Change repository owner
|
||||
if(repository.owner != form.newOwner){
|
||||
// Update database
|
||||
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
|
||||
// Move git repository
|
||||
defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
|
||||
FileUtils.moveDirectory(dir, getRepositoryDir(form.newOwner, repository.name))
|
||||
}
|
||||
// Move wiki repository
|
||||
defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
|
||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(form.newOwner, repository.name))
|
||||
}
|
||||
}
|
||||
redirect(s"/${form.newOwner}/${repository.name}")
|
||||
})
|
||||
|
||||
/**
|
||||
* Delete the repository.
|
||||
*/
|
||||
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
|
||||
deleteRepository(repository.owner, repository.name)
|
||||
|
||||
FileUtils.deleteDirectory(getRepositoryDir(repository.owner, repository.name))
|
||||
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name))
|
||||
FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name))
|
||||
|
||||
redirect(s"/${repository.owner}")
|
||||
})
|
||||
|
||||
/**
|
||||
* Provides duplication check for web hook url.
|
||||
*/
|
||||
private def webHook: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
getWebHookURLs(params("owner"), params("repository")).map(_.url).find(_ == value).map(_ => "URL had been registered already.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides Constraint to validate the collaborator name.
|
||||
*/
|
||||
private def collaborator: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
getAccountByUserName(value) match {
|
||||
case None => Some("User does not exist.")
|
||||
case Some(x) if(x.isGroupAccount)
|
||||
=> Some("User does not exist.")
|
||||
case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
|
||||
=> Some("User can access this repository already.")
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate check for the rename repository name.
|
||||
*/
|
||||
private def renameRepositoryName: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
|
||||
params.get("repository").filter(_ != value).flatMap { _ =>
|
||||
params.get("owner").flatMap { userName =>
|
||||
getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides Constraint to validate the repository transfer user.
|
||||
*/
|
||||
private def transferUser: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
getAccountByUserName(value) match {
|
||||
case None => Some("User does not exist.")
|
||||
case Some(x) => if(x.userName == params("owner")){
|
||||
Some("This is current repository owner.")
|
||||
} else {
|
||||
params.get("repository").flatMap { repositoryName =>
|
||||
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map{ _ => "User already has same repository." }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,39 @@
|
||||
package model
|
||||
|
||||
trait AccountComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
lazy val Accounts = TableQuery[Accounts]
|
||||
|
||||
class Accounts(tag: Tag) extends Table[Account](tag, "ACCOUNT") {
|
||||
val userName = column[String]("USER_NAME", O PrimaryKey)
|
||||
val fullName = column[String]("FULL_NAME")
|
||||
val mailAddress = column[String]("MAIL_ADDRESS")
|
||||
val password = column[String]("PASSWORD")
|
||||
val isAdmin = column[Boolean]("ADMINISTRATOR")
|
||||
val url = column[String]("URL")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
val lastLoginDate = column[java.util.Date]("LAST_LOGIN_DATE")
|
||||
val image = column[String]("IMAGE")
|
||||
val groupAccount = column[Boolean]("GROUP_ACCOUNT")
|
||||
val removed = column[Boolean]("REMOVED")
|
||||
def * = (userName, fullName, mailAddress, password, isAdmin, url.?, registeredDate, updatedDate, lastLoginDate.?, image.?, groupAccount, removed) <> (Account.tupled, Account.unapply)
|
||||
}
|
||||
|
||||
case class Account(
|
||||
userName: String,
|
||||
fullName: String,
|
||||
mailAddress: String,
|
||||
password: String,
|
||||
isAdmin: Boolean,
|
||||
url: Option[String],
|
||||
registeredDate: java.util.Date,
|
||||
updatedDate: java.util.Date,
|
||||
lastLoginDate: Option[java.util.Date],
|
||||
image: Option[String],
|
||||
isGroupAccount: Boolean,
|
||||
isRemoved: Boolean
|
||||
)
|
||||
}
|
||||
package model
|
||||
|
||||
trait AccountComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
lazy val Accounts = TableQuery[Accounts]
|
||||
|
||||
class Accounts(tag: Tag) extends Table[Account](tag, "ACCOUNT") {
|
||||
val userName = column[String]("USER_NAME", O PrimaryKey)
|
||||
val fullName = column[String]("FULL_NAME")
|
||||
val mailAddress = column[String]("MAIL_ADDRESS")
|
||||
val password = column[String]("PASSWORD")
|
||||
val isAdmin = column[Boolean]("ADMINISTRATOR")
|
||||
val url = column[String]("URL")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
val lastLoginDate = column[java.util.Date]("LAST_LOGIN_DATE")
|
||||
val image = column[String]("IMAGE")
|
||||
val groupAccount = column[Boolean]("GROUP_ACCOUNT")
|
||||
val removed = column[Boolean]("REMOVED")
|
||||
def * = (userName, fullName, mailAddress, password, isAdmin, url.?, registeredDate, updatedDate, lastLoginDate.?, image.?, groupAccount, removed) <> (Account.tupled, Account.unapply)
|
||||
}
|
||||
|
||||
case class Account(
|
||||
userName: String,
|
||||
fullName: String,
|
||||
mailAddress: String,
|
||||
password: String,
|
||||
isAdmin: Boolean,
|
||||
url: Option[String],
|
||||
registeredDate: java.util.Date,
|
||||
updatedDate: java.util.Date,
|
||||
lastLoginDate: Option[java.util.Date],
|
||||
image: Option[String],
|
||||
isGroupAccount: Boolean,
|
||||
isRemoved: Boolean
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
package model
|
||||
|
||||
protected[model] trait TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
|
||||
trait BasicTemplate { self: Table[_] =>
|
||||
val userName = column[String]("USER_NAME")
|
||||
val repositoryName = column[String]("REPOSITORY_NAME")
|
||||
|
||||
def byRepository(owner: String, repository: String) =
|
||||
(userName is owner.bind) && (repositoryName is repository.bind)
|
||||
|
||||
def byRepository(userName: Column[String], repositoryName: Column[String]) =
|
||||
(this.userName is userName) && (this.repositoryName is repositoryName)
|
||||
}
|
||||
|
||||
trait IssueTemplate extends BasicTemplate { self: Table[_] =>
|
||||
val issueId = column[Int]("ISSUE_ID")
|
||||
|
||||
def byIssue(owner: String, repository: String, issueId: Int) =
|
||||
byRepository(owner, repository) && (this.issueId is issueId.bind)
|
||||
|
||||
def byIssue(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) =
|
||||
byRepository(userName, repositoryName) && (this.issueId is issueId)
|
||||
}
|
||||
|
||||
trait LabelTemplate extends BasicTemplate { self: Table[_] =>
|
||||
val labelId = column[Int]("LABEL_ID")
|
||||
|
||||
def byLabel(owner: String, repository: String, labelId: Int) =
|
||||
byRepository(owner, repository) && (this.labelId is labelId.bind)
|
||||
|
||||
def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) =
|
||||
byRepository(userName, repositoryName) && (this.labelId is labelId)
|
||||
}
|
||||
|
||||
trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
|
||||
val milestoneId = column[Int]("MILESTONE_ID")
|
||||
|
||||
def byMilestone(owner: String, repository: String, milestoneId: Int) =
|
||||
byRepository(owner, repository) && (this.milestoneId is milestoneId.bind)
|
||||
|
||||
def byMilestone(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) =
|
||||
byRepository(userName, repositoryName) && (this.milestoneId is milestoneId)
|
||||
}
|
||||
|
||||
}
|
||||
package model
|
||||
|
||||
protected[model] trait TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
|
||||
trait BasicTemplate { self: Table[_] =>
|
||||
val userName = column[String]("USER_NAME")
|
||||
val repositoryName = column[String]("REPOSITORY_NAME")
|
||||
|
||||
def byRepository(owner: String, repository: String) =
|
||||
(userName is owner.bind) && (repositoryName is repository.bind)
|
||||
|
||||
def byRepository(userName: Column[String], repositoryName: Column[String]) =
|
||||
(this.userName is userName) && (this.repositoryName is repositoryName)
|
||||
}
|
||||
|
||||
trait IssueTemplate extends BasicTemplate { self: Table[_] =>
|
||||
val issueId = column[Int]("ISSUE_ID")
|
||||
|
||||
def byIssue(owner: String, repository: String, issueId: Int) =
|
||||
byRepository(owner, repository) && (this.issueId is issueId.bind)
|
||||
|
||||
def byIssue(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) =
|
||||
byRepository(userName, repositoryName) && (this.issueId is issueId)
|
||||
}
|
||||
|
||||
trait LabelTemplate extends BasicTemplate { self: Table[_] =>
|
||||
val labelId = column[Int]("LABEL_ID")
|
||||
|
||||
def byLabel(owner: String, repository: String, labelId: Int) =
|
||||
byRepository(owner, repository) && (this.labelId is labelId.bind)
|
||||
|
||||
def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) =
|
||||
byRepository(userName, repositoryName) && (this.labelId is labelId)
|
||||
}
|
||||
|
||||
trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
|
||||
val milestoneId = column[Int]("MILESTONE_ID")
|
||||
|
||||
def byMilestone(owner: String, repository: String, milestoneId: Int) =
|
||||
byRepository(owner, repository) && (this.milestoneId is milestoneId.bind)
|
||||
|
||||
def byMilestone(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) =
|
||||
byRepository(userName, repositoryName) && (this.milestoneId is milestoneId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
package model
|
||||
|
||||
trait IssueComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
lazy val IssueId = TableQuery[IssueId]
|
||||
lazy val IssueOutline = TableQuery[IssueOutline]
|
||||
lazy val Issues = TableQuery[Issues]
|
||||
|
||||
class IssueId(tag: Tag) extends Table[(String, String, Int)](tag, "ISSUE_ID") with IssueTemplate {
|
||||
def * = (userName, repositoryName, issueId)
|
||||
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
|
||||
}
|
||||
|
||||
class IssueOutline(tag: Tag) extends Table[(String, String, Int, Int)](tag, "ISSUE_OUTLINE_VIEW") with IssueTemplate {
|
||||
val commentCount = column[Int]("COMMENT_COUNT")
|
||||
def * = (userName, repositoryName, issueId, commentCount)
|
||||
}
|
||||
|
||||
class Issues(tag: Tag) extends Table[Issue](tag, "ISSUE") with IssueTemplate with MilestoneTemplate {
|
||||
val openedUserName = column[String]("OPENED_USER_NAME")
|
||||
val assignedUserName = column[String]("ASSIGNED_USER_NAME")
|
||||
val title = column[String]("TITLE")
|
||||
val content = column[String]("CONTENT")
|
||||
val closed = column[Boolean]("CLOSED")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
val pullRequest = column[Boolean]("PULL_REQUEST")
|
||||
def * = (userName, repositoryName, issueId, openedUserName, milestoneId.?, assignedUserName.?, title, content.?, closed, registeredDate, updatedDate, pullRequest) <> (Issue.tupled, Issue.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
|
||||
}
|
||||
|
||||
case class Issue(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
issueId: Int,
|
||||
openedUserName: String,
|
||||
milestoneId: Option[Int],
|
||||
assignedUserName: Option[String],
|
||||
title: String,
|
||||
content: Option[String],
|
||||
closed: Boolean,
|
||||
registeredDate: java.util.Date,
|
||||
updatedDate: java.util.Date,
|
||||
isPullRequest: Boolean)
|
||||
}
|
||||
package model
|
||||
|
||||
trait IssueComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
lazy val IssueId = TableQuery[IssueId]
|
||||
lazy val IssueOutline = TableQuery[IssueOutline]
|
||||
lazy val Issues = TableQuery[Issues]
|
||||
|
||||
class IssueId(tag: Tag) extends Table[(String, String, Int)](tag, "ISSUE_ID") with IssueTemplate {
|
||||
def * = (userName, repositoryName, issueId)
|
||||
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
|
||||
}
|
||||
|
||||
class IssueOutline(tag: Tag) extends Table[(String, String, Int, Int)](tag, "ISSUE_OUTLINE_VIEW") with IssueTemplate {
|
||||
val commentCount = column[Int]("COMMENT_COUNT")
|
||||
def * = (userName, repositoryName, issueId, commentCount)
|
||||
}
|
||||
|
||||
class Issues(tag: Tag) extends Table[Issue](tag, "ISSUE") with IssueTemplate with MilestoneTemplate {
|
||||
val openedUserName = column[String]("OPENED_USER_NAME")
|
||||
val assignedUserName = column[String]("ASSIGNED_USER_NAME")
|
||||
val title = column[String]("TITLE")
|
||||
val content = column[String]("CONTENT")
|
||||
val closed = column[Boolean]("CLOSED")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
val pullRequest = column[Boolean]("PULL_REQUEST")
|
||||
def * = (userName, repositoryName, issueId, openedUserName, milestoneId.?, assignedUserName.?, title, content.?, closed, registeredDate, updatedDate, pullRequest) <> (Issue.tupled, Issue.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
|
||||
}
|
||||
|
||||
case class Issue(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
issueId: Int,
|
||||
openedUserName: String,
|
||||
milestoneId: Option[Int],
|
||||
assignedUserName: Option[String],
|
||||
title: String,
|
||||
content: Option[String],
|
||||
closed: Boolean,
|
||||
registeredDate: java.util.Date,
|
||||
updatedDate: java.util.Date,
|
||||
isPullRequest: Boolean)
|
||||
}
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
package model
|
||||
|
||||
trait IssueCommentComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
lazy val IssueComments = new TableQuery(tag => new IssueComments(tag)){
|
||||
def autoInc = this returning this.map(_.commentId)
|
||||
}
|
||||
|
||||
class IssueComments(tag: Tag) extends Table[IssueComment](tag, "ISSUE_COMMENT") with IssueTemplate {
|
||||
val commentId = column[Int]("COMMENT_ID", O AutoInc)
|
||||
val action = column[String]("ACTION")
|
||||
val commentedUserName = column[String]("COMMENTED_USER_NAME")
|
||||
val content = column[String]("CONTENT")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
def * = (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply)
|
||||
|
||||
def byPrimaryKey(commentId: Int) = this.commentId is commentId.bind
|
||||
}
|
||||
|
||||
case class IssueComment(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
issueId: Int,
|
||||
commentId: Int = 0,
|
||||
action: String,
|
||||
commentedUserName: String,
|
||||
content: String,
|
||||
registeredDate: java.util.Date,
|
||||
updatedDate: java.util.Date
|
||||
)
|
||||
}
|
||||
package model
|
||||
|
||||
trait IssueCommentComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
lazy val IssueComments = new TableQuery(tag => new IssueComments(tag)){
|
||||
def autoInc = this returning this.map(_.commentId)
|
||||
}
|
||||
|
||||
class IssueComments(tag: Tag) extends Table[IssueComment](tag, "ISSUE_COMMENT") with IssueTemplate {
|
||||
val commentId = column[Int]("COMMENT_ID", O AutoInc)
|
||||
val action = column[String]("ACTION")
|
||||
val commentedUserName = column[String]("COMMENTED_USER_NAME")
|
||||
val content = column[String]("CONTENT")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
def * = (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply)
|
||||
|
||||
def byPrimaryKey(commentId: Int) = this.commentId is commentId.bind
|
||||
}
|
||||
|
||||
case class IssueComment(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
issueId: Int,
|
||||
commentId: Int = 0,
|
||||
action: String,
|
||||
commentedUserName: String,
|
||||
content: String,
|
||||
registeredDate: java.util.Date,
|
||||
updatedDate: java.util.Date
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package object model extends {
|
||||
// TODO
|
||||
val profile = slick.driver.H2Driver
|
||||
|
||||
} with AccountComponent
|
||||
with ActivityComponent
|
||||
with CollaboratorComponent
|
||||
with GroupMemberComponent
|
||||
with IssueComponent
|
||||
with IssueCommentComponent
|
||||
with IssueLabelComponent
|
||||
with LabelComponent
|
||||
with MilestoneComponent
|
||||
with PullRequestComponent
|
||||
with RepositoryComponent
|
||||
with SshKeyComponent
|
||||
with WebHookComponent with Profile {
|
||||
/**
|
||||
* Returns system date.
|
||||
*/
|
||||
def currentDate = new java.util.Date()
|
||||
}
|
||||
package object model extends {
|
||||
// TODO
|
||||
val profile = slick.driver.H2Driver
|
||||
|
||||
} with AccountComponent
|
||||
with ActivityComponent
|
||||
with CollaboratorComponent
|
||||
with GroupMemberComponent
|
||||
with IssueComponent
|
||||
with IssueCommentComponent
|
||||
with IssueLabelComponent
|
||||
with LabelComponent
|
||||
with MilestoneComponent
|
||||
with PullRequestComponent
|
||||
with RepositoryComponent
|
||||
with SshKeyComponent
|
||||
with WebHookComponent with Profile {
|
||||
/**
|
||||
* Returns system date.
|
||||
*/
|
||||
def currentDate = new java.util.Date()
|
||||
}
|
||||
|
||||
@@ -1,385 +1,385 @@
|
||||
package service
|
||||
|
||||
import scala.slick.jdbc.{StaticQuery => Q}
|
||||
import Q.interpolation
|
||||
|
||||
import model._
|
||||
import profile.simple._
|
||||
import util.Implicits._
|
||||
import util.StringUtil._
|
||||
|
||||
trait IssuesService {
|
||||
import IssuesService._
|
||||
|
||||
def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) =
|
||||
if (issueId forall (_.isDigit))
|
||||
Issues filter (_.byPrimaryKey(owner, repository, issueId.toInt)) firstOption
|
||||
else None
|
||||
|
||||
def getComments(owner: String, repository: String, issueId: Int)(implicit s: Session) =
|
||||
IssueComments filter (_.byIssue(owner, repository, issueId)) list
|
||||
|
||||
def getComment(owner: String, repository: String, commentId: String)(implicit s: Session) =
|
||||
if (commentId forall (_.isDigit))
|
||||
IssueComments filter { t =>
|
||||
t.byPrimaryKey(commentId.toInt) && t.byRepository(owner, repository)
|
||||
} firstOption
|
||||
else None
|
||||
|
||||
def getIssueLabels(owner: String, repository: String, issueId: Int)(implicit s: Session) =
|
||||
IssueLabels
|
||||
.innerJoin(Labels).on { (t1, t2) =>
|
||||
t1.byLabel(t2.userName, t2.repositoryName, t2.labelId)
|
||||
}
|
||||
.filter ( _._1.byIssue(owner, repository, issueId) )
|
||||
.map ( _._2 )
|
||||
.list
|
||||
|
||||
def getIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session) =
|
||||
IssueLabels filter (_.byPrimaryKey(owner, repository, issueId, labelId)) firstOption
|
||||
|
||||
/**
|
||||
* Returns the count of the search result against issues.
|
||||
*
|
||||
* @param condition the search condition
|
||||
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
|
||||
* @param onlyPullRequest if true then counts only pull request, false then counts both of issue and pull request.
|
||||
* @param repos Tuple of the repository owner and the repository name
|
||||
* @return the count of the search result
|
||||
*/
|
||||
def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
|
||||
repos: (String, String)*)(implicit s: Session): Int =
|
||||
// TODO check SQL
|
||||
Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first
|
||||
/**
|
||||
* Returns the Map which contains issue count for each labels.
|
||||
*
|
||||
* @param owner the repository owner
|
||||
* @param repository the repository name
|
||||
* @param condition the search condition
|
||||
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
|
||||
* @return the Map which contains issue count for each labels (key is label name, value is issue count)
|
||||
*/
|
||||
def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition,
|
||||
filterUser: Map[String, String])(implicit s: Session): Map[String, Int] = {
|
||||
|
||||
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), filterUser, false)
|
||||
.innerJoin(IssueLabels).on { (t1, t2) =>
|
||||
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
||||
}
|
||||
.innerJoin(Labels).on { case ((t1, t2), t3) =>
|
||||
t2.byLabel(t3.userName, t3.repositoryName, t3.labelId)
|
||||
}
|
||||
.groupBy { case ((t1, t2), t3) =>
|
||||
t3.labelName
|
||||
}
|
||||
.map { case (labelName, t) =>
|
||||
labelName -> t.length
|
||||
}
|
||||
.toMap
|
||||
}
|
||||
/**
|
||||
* Returns list which contains issue count for each repository.
|
||||
* If the issue does not exist, its repository is not included in the result.
|
||||
*
|
||||
* @param condition the search condition
|
||||
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
|
||||
* @param onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request.
|
||||
* @param repos Tuple of the repository owner and the repository name
|
||||
* @return list which contains issue count for each repository
|
||||
*/
|
||||
def countIssueGroupByRepository(
|
||||
condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
|
||||
repos: (String, String)*)(implicit s: Session): List[(String, String, Int)] = {
|
||||
searchIssueQuery(repos, condition.copy(repo = None), filterUser, onlyPullRequest)
|
||||
.groupBy { t =>
|
||||
t.userName -> t.repositoryName
|
||||
}
|
||||
.map { case (repo, t) =>
|
||||
(repo._1, repo._2, t.length)
|
||||
}
|
||||
.sortBy(_._3 desc)
|
||||
.list
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the search result against issues.
|
||||
*
|
||||
* @param condition the search condition
|
||||
* @param filterUser the filter user name (key is "all", "assigned", "created_by" or "not_created_by", value is the user name)
|
||||
* @param onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request.
|
||||
* @param offset the offset for pagination
|
||||
* @param limit the limit for pagination
|
||||
* @param repos Tuple of the repository owner and the repository name
|
||||
* @return the search result (list of tuples which contain issue, labels and comment count)
|
||||
*/
|
||||
def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
|
||||
offset: Int, limit: Int, repos: (String, String)*)
|
||||
(implicit s: Session): List[(Issue, List[Label], Int)] = {
|
||||
|
||||
// get issues and comment count and labels
|
||||
searchIssueQuery(repos, condition, filterUser, onlyPullRequest)
|
||||
.innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) }
|
||||
.sortBy { case (t1, t2) =>
|
||||
(condition.sort match {
|
||||
case "created" => t1.registeredDate
|
||||
case "comments" => t2.commentCount
|
||||
case "updated" => t1.updatedDate
|
||||
}) match {
|
||||
case sort => condition.direction match {
|
||||
case "asc" => sort asc
|
||||
case "desc" => sort desc
|
||||
}
|
||||
}
|
||||
}
|
||||
.drop(offset).take(limit)
|
||||
.leftJoin (IssueLabels) .on { case ((t1, t2), t3) => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) }
|
||||
.leftJoin (Labels) .on { case (((t1, t2), t3), t4) => t3.byLabel(t4.userName, t4.repositoryName, t4.labelId) }
|
||||
.map { case (((t1, t2), t3), t4) =>
|
||||
(t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?)
|
||||
}
|
||||
.list
|
||||
.splitWith { (c1, c2) =>
|
||||
c1._1.userName == c2._1.userName &&
|
||||
c1._1.repositoryName == c2._1.repositoryName &&
|
||||
c1._1.issueId == c2._1.issueId
|
||||
}
|
||||
.map { issues => issues.head match {
|
||||
case (issue, commentCount, _,_,_) =>
|
||||
(issue,
|
||||
issues.flatMap { t => t._3.map (
|
||||
Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get)
|
||||
)} toList,
|
||||
commentCount)
|
||||
}} toList
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles query for conditional issue searching.
|
||||
*/
|
||||
private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition,
|
||||
filterUser: Map[String, String], onlyPullRequest: Boolean)(implicit s: Session) =
|
||||
Issues filter { t1 =>
|
||||
condition.repo
|
||||
.map { _.split('/') match { case array => Seq(array(0) -> array(1)) } }
|
||||
.getOrElse (repos)
|
||||
.map { case (owner, repository) => t1.byRepository(owner, repository) }
|
||||
.foldLeft[Column[Boolean]](false) ( _ || _ ) &&
|
||||
(t1.closed is (condition.state == "closed").bind) &&
|
||||
(t1.milestoneId is condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
|
||||
(t1.milestoneId isNull, condition.milestoneId == Some(None)) &&
|
||||
(t1.assignedUserName is filterUser("assigned").bind, filterUser.get("assigned").isDefined) &&
|
||||
(t1.openedUserName is filterUser("created_by").bind, filterUser.get("created_by").isDefined) &&
|
||||
(t1.openedUserName isNot filterUser("not_created_by").bind, filterUser.get("not_created_by").isDefined) &&
|
||||
(t1.pullRequest is true.bind, onlyPullRequest) &&
|
||||
(IssueLabels filter { t2 =>
|
||||
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
|
||||
(t2.labelId in
|
||||
(Labels filter { t3 =>
|
||||
(t3.byRepository(t1.userName, t1.repositoryName)) &&
|
||||
(t3.labelName inSetBind condition.labels)
|
||||
} map(_.labelId)))
|
||||
} exists, condition.labels.nonEmpty)
|
||||
}
|
||||
|
||||
def createIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String],
|
||||
assignedUserName: Option[String], milestoneId: Option[Int],
|
||||
isPullRequest: Boolean = false)(implicit s: Session) =
|
||||
// next id number
|
||||
sql"SELECT ISSUE_ID + 1 FROM ISSUE_ID WHERE USER_NAME = $owner AND REPOSITORY_NAME = $repository FOR UPDATE".as[Int]
|
||||
.firstOption.filter { id =>
|
||||
Issues insert Issue(
|
||||
owner,
|
||||
repository,
|
||||
id,
|
||||
loginUser,
|
||||
milestoneId,
|
||||
assignedUserName,
|
||||
title,
|
||||
content,
|
||||
false,
|
||||
currentDate,
|
||||
currentDate,
|
||||
isPullRequest)
|
||||
|
||||
// increment issue id
|
||||
IssueId
|
||||
.filter (_.byPrimaryKey(owner, repository))
|
||||
.map (_.issueId)
|
||||
.update (id) > 0
|
||||
} get
|
||||
|
||||
def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session) =
|
||||
IssueLabels insert IssueLabel(owner, repository, issueId, labelId)
|
||||
|
||||
def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session) =
|
||||
IssueLabels filter(_.byPrimaryKey(owner, repository, issueId, labelId)) delete
|
||||
|
||||
def createComment(owner: String, repository: String, loginUser: String,
|
||||
issueId: Int, content: String, action: String)(implicit s: Session): Int =
|
||||
IssueComments.autoInc insert IssueComment(
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
issueId = issueId,
|
||||
action = action,
|
||||
commentedUserName = loginUser,
|
||||
content = content,
|
||||
registeredDate = currentDate,
|
||||
updatedDate = currentDate)
|
||||
|
||||
def updateIssue(owner: String, repository: String, issueId: Int,
|
||||
title: String, content: Option[String])(implicit s: Session) =
|
||||
Issues
|
||||
.filter (_.byPrimaryKey(owner, repository, issueId))
|
||||
.map { t =>
|
||||
(t.title, t.content.?, t.updatedDate)
|
||||
}
|
||||
.update (title, content, currentDate)
|
||||
|
||||
def updateAssignedUserName(owner: String, repository: String, issueId: Int,
|
||||
assignedUserName: Option[String])(implicit s: Session) =
|
||||
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.assignedUserName?).update (assignedUserName)
|
||||
|
||||
def updateMilestoneId(owner: String, repository: String, issueId: Int,
|
||||
milestoneId: Option[Int])(implicit s: Session) =
|
||||
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.milestoneId?).update (milestoneId)
|
||||
|
||||
def updateComment(commentId: Int, content: String)(implicit s: Session) =
|
||||
IssueComments
|
||||
.filter (_.byPrimaryKey(commentId))
|
||||
.map { t =>
|
||||
t.content -> t.updatedDate
|
||||
}
|
||||
.update (content, currentDate)
|
||||
|
||||
def deleteComment(commentId: Int)(implicit s: Session) =
|
||||
IssueComments filter (_.byPrimaryKey(commentId)) delete
|
||||
|
||||
def updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean)(implicit s: Session) =
|
||||
Issues
|
||||
.filter (_.byPrimaryKey(owner, repository, issueId))
|
||||
.map { t =>
|
||||
t.closed -> t.updatedDate
|
||||
}
|
||||
.update (closed, currentDate)
|
||||
|
||||
/**
|
||||
* Search issues by keyword.
|
||||
*
|
||||
* @param owner the repository owner
|
||||
* @param repository the repository name
|
||||
* @param query the keywords separated by whitespace.
|
||||
* @return issues with comment count and matched content of issue or comment
|
||||
*/
|
||||
def searchIssuesByKeyword(owner: String, repository: String, query: String)
|
||||
(implicit s: Session): List[(Issue, Int, String)] = {
|
||||
import slick.driver.JdbcDriver.likeEncode
|
||||
val keywords = splitWords(query.toLowerCase)
|
||||
|
||||
// Search Issue
|
||||
val issues = Issues
|
||||
.innerJoin(IssueOutline).on { case (t1, t2) =>
|
||||
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
||||
}
|
||||
.filter { case (t1, t2) =>
|
||||
keywords.map { keyword =>
|
||||
(t1.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) ||
|
||||
(t1.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^'))
|
||||
} .reduceLeft(_ && _)
|
||||
}
|
||||
.map { case (t1, t2) =>
|
||||
(t1, 0, t1.content.?, t2.commentCount)
|
||||
}
|
||||
|
||||
// Search IssueComment
|
||||
val comments = IssueComments
|
||||
.innerJoin(Issues).on { case (t1, t2) =>
|
||||
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
||||
}
|
||||
.innerJoin(IssueOutline).on { case ((t1, t2), t3) =>
|
||||
t2.byIssue(t3.userName, t3.repositoryName, t3.issueId)
|
||||
}
|
||||
.filter { case ((t1, t2), t3) =>
|
||||
keywords.map { query =>
|
||||
t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^')
|
||||
}.reduceLeft(_ && _)
|
||||
}
|
||||
.map { case ((t1, t2), t3) =>
|
||||
(t2, t1.commentId, t1.content.?, t3.commentCount)
|
||||
}
|
||||
|
||||
issues.union(comments).sortBy { case (issue, commentId, _, _) =>
|
||||
issue.issueId -> commentId
|
||||
}.list.splitWith { case ((issue1, _, _, _), (issue2, _, _, _)) =>
|
||||
issue1.issueId == issue2.issueId
|
||||
}.map { _.head match {
|
||||
case (issue, _, content, commentCount) => (issue, commentCount, content.getOrElse(""))
|
||||
}
|
||||
}.toList
|
||||
}
|
||||
|
||||
def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String)(implicit s: Session) = {
|
||||
extractCloseId(message).foreach { issueId =>
|
||||
for(issue <- getIssue(owner, repository, issueId) if !issue.closed){
|
||||
createComment(owner, repository, userName, issue.issueId, "Close", "close")
|
||||
updateClosed(owner, repository, issue.issueId, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object IssuesService {
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
val IssueLimit = 30
|
||||
|
||||
case class IssueSearchCondition(
|
||||
labels: Set[String] = Set.empty,
|
||||
milestoneId: Option[Option[Int]] = None,
|
||||
repo: Option[String] = None,
|
||||
state: String = "open",
|
||||
sort: String = "created",
|
||||
direction: String = "desc"){
|
||||
|
||||
def toURL: String =
|
||||
"?" + List(
|
||||
if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
|
||||
milestoneId.map { id => "milestone=" + (id match {
|
||||
case Some(x) => x.toString
|
||||
case None => "none"
|
||||
})},
|
||||
repo.map("for=" + urlEncode(_)),
|
||||
Some("state=" + urlEncode(state)),
|
||||
Some("sort=" + urlEncode(sort)),
|
||||
Some("direction=" + urlEncode(direction))).flatten.mkString("&")
|
||||
|
||||
}
|
||||
|
||||
object IssueSearchCondition {
|
||||
|
||||
private def param(request: HttpServletRequest, name: String, allow: Seq[String] = Nil): Option[String] = {
|
||||
val value = request.getParameter(name)
|
||||
if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
|
||||
}
|
||||
|
||||
def apply(request: HttpServletRequest): IssueSearchCondition =
|
||||
IssueSearchCondition(
|
||||
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
|
||||
param(request, "milestone").map{
|
||||
case "none" => None
|
||||
case x => x.toIntOpt
|
||||
},
|
||||
param(request, "for"),
|
||||
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
||||
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
|
||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"))
|
||||
|
||||
def page(request: HttpServletRequest) = try {
|
||||
val i = param(request, "page").getOrElse("1").toInt
|
||||
if(i <= 0) 1 else i
|
||||
} catch {
|
||||
case e: NumberFormatException => 1
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
package service
|
||||
|
||||
import scala.slick.jdbc.{StaticQuery => Q}
|
||||
import Q.interpolation
|
||||
|
||||
import model._
|
||||
import profile.simple._
|
||||
import util.Implicits._
|
||||
import util.StringUtil._
|
||||
|
||||
trait IssuesService {
|
||||
import IssuesService._
|
||||
|
||||
def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) =
|
||||
if (issueId forall (_.isDigit))
|
||||
Issues filter (_.byPrimaryKey(owner, repository, issueId.toInt)) firstOption
|
||||
else None
|
||||
|
||||
def getComments(owner: String, repository: String, issueId: Int)(implicit s: Session) =
|
||||
IssueComments filter (_.byIssue(owner, repository, issueId)) list
|
||||
|
||||
def getComment(owner: String, repository: String, commentId: String)(implicit s: Session) =
|
||||
if (commentId forall (_.isDigit))
|
||||
IssueComments filter { t =>
|
||||
t.byPrimaryKey(commentId.toInt) && t.byRepository(owner, repository)
|
||||
} firstOption
|
||||
else None
|
||||
|
||||
def getIssueLabels(owner: String, repository: String, issueId: Int)(implicit s: Session) =
|
||||
IssueLabels
|
||||
.innerJoin(Labels).on { (t1, t2) =>
|
||||
t1.byLabel(t2.userName, t2.repositoryName, t2.labelId)
|
||||
}
|
||||
.filter ( _._1.byIssue(owner, repository, issueId) )
|
||||
.map ( _._2 )
|
||||
.list
|
||||
|
||||
def getIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session) =
|
||||
IssueLabels filter (_.byPrimaryKey(owner, repository, issueId, labelId)) firstOption
|
||||
|
||||
/**
|
||||
* Returns the count of the search result against issues.
|
||||
*
|
||||
* @param condition the search condition
|
||||
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
|
||||
* @param onlyPullRequest if true then counts only pull request, false then counts both of issue and pull request.
|
||||
* @param repos Tuple of the repository owner and the repository name
|
||||
* @return the count of the search result
|
||||
*/
|
||||
def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
|
||||
repos: (String, String)*)(implicit s: Session): Int =
|
||||
// TODO check SQL
|
||||
Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first
|
||||
/**
|
||||
* Returns the Map which contains issue count for each labels.
|
||||
*
|
||||
* @param owner the repository owner
|
||||
* @param repository the repository name
|
||||
* @param condition the search condition
|
||||
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
|
||||
* @return the Map which contains issue count for each labels (key is label name, value is issue count)
|
||||
*/
|
||||
def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition,
|
||||
filterUser: Map[String, String])(implicit s: Session): Map[String, Int] = {
|
||||
|
||||
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), filterUser, false)
|
||||
.innerJoin(IssueLabels).on { (t1, t2) =>
|
||||
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
||||
}
|
||||
.innerJoin(Labels).on { case ((t1, t2), t3) =>
|
||||
t2.byLabel(t3.userName, t3.repositoryName, t3.labelId)
|
||||
}
|
||||
.groupBy { case ((t1, t2), t3) =>
|
||||
t3.labelName
|
||||
}
|
||||
.map { case (labelName, t) =>
|
||||
labelName -> t.length
|
||||
}
|
||||
.toMap
|
||||
}
|
||||
/**
|
||||
* Returns list which contains issue count for each repository.
|
||||
* If the issue does not exist, its repository is not included in the result.
|
||||
*
|
||||
* @param condition the search condition
|
||||
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name)
|
||||
* @param onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request.
|
||||
* @param repos Tuple of the repository owner and the repository name
|
||||
* @return list which contains issue count for each repository
|
||||
*/
|
||||
def countIssueGroupByRepository(
|
||||
condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
|
||||
repos: (String, String)*)(implicit s: Session): List[(String, String, Int)] = {
|
||||
searchIssueQuery(repos, condition.copy(repo = None), filterUser, onlyPullRequest)
|
||||
.groupBy { t =>
|
||||
t.userName -> t.repositoryName
|
||||
}
|
||||
.map { case (repo, t) =>
|
||||
(repo._1, repo._2, t.length)
|
||||
}
|
||||
.sortBy(_._3 desc)
|
||||
.list
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the search result against issues.
|
||||
*
|
||||
* @param condition the search condition
|
||||
* @param filterUser the filter user name (key is "all", "assigned", "created_by" or "not_created_by", value is the user name)
|
||||
* @param onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request.
|
||||
* @param offset the offset for pagination
|
||||
* @param limit the limit for pagination
|
||||
* @param repos Tuple of the repository owner and the repository name
|
||||
* @return the search result (list of tuples which contain issue, labels and comment count)
|
||||
*/
|
||||
def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
|
||||
offset: Int, limit: Int, repos: (String, String)*)
|
||||
(implicit s: Session): List[(Issue, List[Label], Int)] = {
|
||||
|
||||
// get issues and comment count and labels
|
||||
searchIssueQuery(repos, condition, filterUser, onlyPullRequest)
|
||||
.innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) }
|
||||
.sortBy { case (t1, t2) =>
|
||||
(condition.sort match {
|
||||
case "created" => t1.registeredDate
|
||||
case "comments" => t2.commentCount
|
||||
case "updated" => t1.updatedDate
|
||||
}) match {
|
||||
case sort => condition.direction match {
|
||||
case "asc" => sort asc
|
||||
case "desc" => sort desc
|
||||
}
|
||||
}
|
||||
}
|
||||
.drop(offset).take(limit)
|
||||
.leftJoin (IssueLabels) .on { case ((t1, t2), t3) => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) }
|
||||
.leftJoin (Labels) .on { case (((t1, t2), t3), t4) => t3.byLabel(t4.userName, t4.repositoryName, t4.labelId) }
|
||||
.map { case (((t1, t2), t3), t4) =>
|
||||
(t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?)
|
||||
}
|
||||
.list
|
||||
.splitWith { (c1, c2) =>
|
||||
c1._1.userName == c2._1.userName &&
|
||||
c1._1.repositoryName == c2._1.repositoryName &&
|
||||
c1._1.issueId == c2._1.issueId
|
||||
}
|
||||
.map { issues => issues.head match {
|
||||
case (issue, commentCount, _,_,_) =>
|
||||
(issue,
|
||||
issues.flatMap { t => t._3.map (
|
||||
Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get)
|
||||
)} toList,
|
||||
commentCount)
|
||||
}} toList
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles query for conditional issue searching.
|
||||
*/
|
||||
private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition,
|
||||
filterUser: Map[String, String], onlyPullRequest: Boolean)(implicit s: Session) =
|
||||
Issues filter { t1 =>
|
||||
condition.repo
|
||||
.map { _.split('/') match { case array => Seq(array(0) -> array(1)) } }
|
||||
.getOrElse (repos)
|
||||
.map { case (owner, repository) => t1.byRepository(owner, repository) }
|
||||
.foldLeft[Column[Boolean]](false) ( _ || _ ) &&
|
||||
(t1.closed is (condition.state == "closed").bind) &&
|
||||
(t1.milestoneId is condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
|
||||
(t1.milestoneId isNull, condition.milestoneId == Some(None)) &&
|
||||
(t1.assignedUserName is filterUser("assigned").bind, filterUser.get("assigned").isDefined) &&
|
||||
(t1.openedUserName is filterUser("created_by").bind, filterUser.get("created_by").isDefined) &&
|
||||
(t1.openedUserName isNot filterUser("not_created_by").bind, filterUser.get("not_created_by").isDefined) &&
|
||||
(t1.pullRequest is true.bind, onlyPullRequest) &&
|
||||
(IssueLabels filter { t2 =>
|
||||
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
|
||||
(t2.labelId in
|
||||
(Labels filter { t3 =>
|
||||
(t3.byRepository(t1.userName, t1.repositoryName)) &&
|
||||
(t3.labelName inSetBind condition.labels)
|
||||
} map(_.labelId)))
|
||||
} exists, condition.labels.nonEmpty)
|
||||
}
|
||||
|
||||
def createIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String],
|
||||
assignedUserName: Option[String], milestoneId: Option[Int],
|
||||
isPullRequest: Boolean = false)(implicit s: Session) =
|
||||
// next id number
|
||||
sql"SELECT ISSUE_ID + 1 FROM ISSUE_ID WHERE USER_NAME = $owner AND REPOSITORY_NAME = $repository FOR UPDATE".as[Int]
|
||||
.firstOption.filter { id =>
|
||||
Issues insert Issue(
|
||||
owner,
|
||||
repository,
|
||||
id,
|
||||
loginUser,
|
||||
milestoneId,
|
||||
assignedUserName,
|
||||
title,
|
||||
content,
|
||||
false,
|
||||
currentDate,
|
||||
currentDate,
|
||||
isPullRequest)
|
||||
|
||||
// increment issue id
|
||||
IssueId
|
||||
.filter (_.byPrimaryKey(owner, repository))
|
||||
.map (_.issueId)
|
||||
.update (id) > 0
|
||||
} get
|
||||
|
||||
def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session) =
|
||||
IssueLabels insert IssueLabel(owner, repository, issueId, labelId)
|
||||
|
||||
def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session) =
|
||||
IssueLabels filter(_.byPrimaryKey(owner, repository, issueId, labelId)) delete
|
||||
|
||||
def createComment(owner: String, repository: String, loginUser: String,
|
||||
issueId: Int, content: String, action: String)(implicit s: Session): Int =
|
||||
IssueComments.autoInc insert IssueComment(
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
issueId = issueId,
|
||||
action = action,
|
||||
commentedUserName = loginUser,
|
||||
content = content,
|
||||
registeredDate = currentDate,
|
||||
updatedDate = currentDate)
|
||||
|
||||
def updateIssue(owner: String, repository: String, issueId: Int,
|
||||
title: String, content: Option[String])(implicit s: Session) =
|
||||
Issues
|
||||
.filter (_.byPrimaryKey(owner, repository, issueId))
|
||||
.map { t =>
|
||||
(t.title, t.content.?, t.updatedDate)
|
||||
}
|
||||
.update (title, content, currentDate)
|
||||
|
||||
def updateAssignedUserName(owner: String, repository: String, issueId: Int,
|
||||
assignedUserName: Option[String])(implicit s: Session) =
|
||||
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.assignedUserName?).update (assignedUserName)
|
||||
|
||||
def updateMilestoneId(owner: String, repository: String, issueId: Int,
|
||||
milestoneId: Option[Int])(implicit s: Session) =
|
||||
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.milestoneId?).update (milestoneId)
|
||||
|
||||
def updateComment(commentId: Int, content: String)(implicit s: Session) =
|
||||
IssueComments
|
||||
.filter (_.byPrimaryKey(commentId))
|
||||
.map { t =>
|
||||
t.content -> t.updatedDate
|
||||
}
|
||||
.update (content, currentDate)
|
||||
|
||||
def deleteComment(commentId: Int)(implicit s: Session) =
|
||||
IssueComments filter (_.byPrimaryKey(commentId)) delete
|
||||
|
||||
def updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean)(implicit s: Session) =
|
||||
Issues
|
||||
.filter (_.byPrimaryKey(owner, repository, issueId))
|
||||
.map { t =>
|
||||
t.closed -> t.updatedDate
|
||||
}
|
||||
.update (closed, currentDate)
|
||||
|
||||
/**
|
||||
* Search issues by keyword.
|
||||
*
|
||||
* @param owner the repository owner
|
||||
* @param repository the repository name
|
||||
* @param query the keywords separated by whitespace.
|
||||
* @return issues with comment count and matched content of issue or comment
|
||||
*/
|
||||
def searchIssuesByKeyword(owner: String, repository: String, query: String)
|
||||
(implicit s: Session): List[(Issue, Int, String)] = {
|
||||
import slick.driver.JdbcDriver.likeEncode
|
||||
val keywords = splitWords(query.toLowerCase)
|
||||
|
||||
// Search Issue
|
||||
val issues = Issues
|
||||
.innerJoin(IssueOutline).on { case (t1, t2) =>
|
||||
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
||||
}
|
||||
.filter { case (t1, t2) =>
|
||||
keywords.map { keyword =>
|
||||
(t1.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) ||
|
||||
(t1.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^'))
|
||||
} .reduceLeft(_ && _)
|
||||
}
|
||||
.map { case (t1, t2) =>
|
||||
(t1, 0, t1.content.?, t2.commentCount)
|
||||
}
|
||||
|
||||
// Search IssueComment
|
||||
val comments = IssueComments
|
||||
.innerJoin(Issues).on { case (t1, t2) =>
|
||||
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
||||
}
|
||||
.innerJoin(IssueOutline).on { case ((t1, t2), t3) =>
|
||||
t2.byIssue(t3.userName, t3.repositoryName, t3.issueId)
|
||||
}
|
||||
.filter { case ((t1, t2), t3) =>
|
||||
keywords.map { query =>
|
||||
t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^')
|
||||
}.reduceLeft(_ && _)
|
||||
}
|
||||
.map { case ((t1, t2), t3) =>
|
||||
(t2, t1.commentId, t1.content.?, t3.commentCount)
|
||||
}
|
||||
|
||||
issues.union(comments).sortBy { case (issue, commentId, _, _) =>
|
||||
issue.issueId -> commentId
|
||||
}.list.splitWith { case ((issue1, _, _, _), (issue2, _, _, _)) =>
|
||||
issue1.issueId == issue2.issueId
|
||||
}.map { _.head match {
|
||||
case (issue, _, content, commentCount) => (issue, commentCount, content.getOrElse(""))
|
||||
}
|
||||
}.toList
|
||||
}
|
||||
|
||||
def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String)(implicit s: Session) = {
|
||||
extractCloseId(message).foreach { issueId =>
|
||||
for(issue <- getIssue(owner, repository, issueId) if !issue.closed){
|
||||
createComment(owner, repository, userName, issue.issueId, "Close", "close")
|
||||
updateClosed(owner, repository, issue.issueId, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object IssuesService {
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
val IssueLimit = 30
|
||||
|
||||
case class IssueSearchCondition(
|
||||
labels: Set[String] = Set.empty,
|
||||
milestoneId: Option[Option[Int]] = None,
|
||||
repo: Option[String] = None,
|
||||
state: String = "open",
|
||||
sort: String = "created",
|
||||
direction: String = "desc"){
|
||||
|
||||
def toURL: String =
|
||||
"?" + List(
|
||||
if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
|
||||
milestoneId.map { id => "milestone=" + (id match {
|
||||
case Some(x) => x.toString
|
||||
case None => "none"
|
||||
})},
|
||||
repo.map("for=" + urlEncode(_)),
|
||||
Some("state=" + urlEncode(state)),
|
||||
Some("sort=" + urlEncode(sort)),
|
||||
Some("direction=" + urlEncode(direction))).flatten.mkString("&")
|
||||
|
||||
}
|
||||
|
||||
object IssueSearchCondition {
|
||||
|
||||
private def param(request: HttpServletRequest, name: String, allow: Seq[String] = Nil): Option[String] = {
|
||||
val value = request.getParameter(name)
|
||||
if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
|
||||
}
|
||||
|
||||
def apply(request: HttpServletRequest): IssueSearchCondition =
|
||||
IssueSearchCondition(
|
||||
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
|
||||
param(request, "milestone").map{
|
||||
case "none" => None
|
||||
case x => x.toIntOpt
|
||||
},
|
||||
param(request, "for"),
|
||||
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
||||
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
|
||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"))
|
||||
|
||||
def page(request: HttpServletRequest) = try {
|
||||
val i = param(request, "page").getOrElse("1").toInt
|
||||
if(i <= 0) 1 else i
|
||||
} catch {
|
||||
case e: NumberFormatException => 1
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
package servlet
|
||||
|
||||
import javax.servlet._
|
||||
import org.slf4j.LoggerFactory
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import util.Keys
|
||||
|
||||
/**
|
||||
* Controls the transaction with the open session in view pattern.
|
||||
*/
|
||||
class TransactionFilter extends Filter {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[TransactionFilter])
|
||||
|
||||
def init(config: FilterConfig) = {}
|
||||
|
||||
def destroy(): Unit = {}
|
||||
|
||||
def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
|
||||
if(req.asInstanceOf[HttpServletRequest].getRequestURI().startsWith("/assets/")){
|
||||
// assets don't need transaction
|
||||
chain.doFilter(req, res)
|
||||
} else {
|
||||
Database(req.getServletContext) withTransaction { session =>
|
||||
logger.debug("begin transaction")
|
||||
req.setAttribute(Keys.Request.DBSession, session)
|
||||
chain.doFilter(req, res)
|
||||
logger.debug("end transaction")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object Database {
|
||||
def apply(context: ServletContext): slick.jdbc.JdbcBackend.Database =
|
||||
slick.jdbc.JdbcBackend.Database.forURL(context.getInitParameter("db.url"),
|
||||
context.getInitParameter("db.user"),
|
||||
context.getInitParameter("db.password"))
|
||||
|
||||
def getSession(req: ServletRequest): slick.jdbc.JdbcBackend#Session =
|
||||
req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
|
||||
|
||||
}
|
||||
package servlet
|
||||
|
||||
import javax.servlet._
|
||||
import org.slf4j.LoggerFactory
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import util.Keys
|
||||
|
||||
/**
|
||||
* Controls the transaction with the open session in view pattern.
|
||||
*/
|
||||
class TransactionFilter extends Filter {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[TransactionFilter])
|
||||
|
||||
def init(config: FilterConfig) = {}
|
||||
|
||||
def destroy(): Unit = {}
|
||||
|
||||
def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
|
||||
if(req.asInstanceOf[HttpServletRequest].getRequestURI().startsWith("/assets/")){
|
||||
// assets don't need transaction
|
||||
chain.doFilter(req, res)
|
||||
} else {
|
||||
Database(req.getServletContext) withTransaction { session =>
|
||||
logger.debug("begin transaction")
|
||||
req.setAttribute(Keys.Request.DBSession, session)
|
||||
chain.doFilter(req, res)
|
||||
logger.debug("end transaction")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object Database {
|
||||
def apply(context: ServletContext): slick.jdbc.JdbcBackend.Database =
|
||||
slick.jdbc.JdbcBackend.Database.forURL(context.getInitParameter("db.url"),
|
||||
context.getInitParameter("db.user"),
|
||||
context.getInitParameter("db.password"))
|
||||
|
||||
def getSession(req: ServletRequest): slick.jdbc.JdbcBackend#Session =
|
||||
req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
|
||||
|
||||
}
|
||||
|
||||
@@ -1,117 +1,117 @@
|
||||
package util
|
||||
|
||||
import scala.concurrent._
|
||||
import ExecutionContext.Implicits.global
|
||||
import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail}
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import app.Context
|
||||
import service.{AccountService, RepositoryService, IssuesService, SystemSettingsService}
|
||||
import servlet.Database
|
||||
import SystemSettingsService.Smtp
|
||||
import _root_.util.ControlUtil.defining
|
||||
import model.profile.simple.Session
|
||||
|
||||
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 session: Session, 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(_) filterNot (_.isGroupAccount) foreach (x => notify(x.mailAddress)) )
|
||||
|
||||
}
|
||||
|
||||
object Notifier {
|
||||
// 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}<br/>
|
||||
|--<br/>
|
||||
|<a href="${url}">View it on GitBucket</a>
|
||||
""".stripMargin
|
||||
|
||||
def msgPullRequest(url: String) = (content: String) => s"""
|
||||
|${content}<hr/>
|
||||
|View, comment on, or merge it at:<br/>
|
||||
|<a href="${url}">${url}</a>
|
||||
""".stripMargin
|
||||
|
||||
def msgComment(url: String) = (content: String) => s"""
|
||||
|${content}<br/>
|
||||
|--<br/>
|
||||
|<a href="${url}">View it on GitBucket</a>
|
||||
""".stripMargin
|
||||
|
||||
def msgStatus(url: String) = (content: String) => s"""
|
||||
|${content} <a href="${url}">#${url split('/') last}</a>
|
||||
""".stripMargin
|
||||
}
|
||||
|
||||
class Mailer(private val smtp: Smtp) extends Notifier {
|
||||
private val logger = LoggerFactory.getLogger(classOf[Mailer])
|
||||
|
||||
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
|
||||
(msg: String => String)(implicit context: Context) = {
|
||||
val database = Database(context.request.getServletContext)
|
||||
|
||||
val f = future {
|
||||
// TODO Can we use the Database Session in other than Transaction Filter?
|
||||
database withSession { implicit session =>
|
||||
getIssue(r.owner, r.name, issueId.toString) foreach { issue =>
|
||||
defining(
|
||||
s"[${r.name}] ${issue.title} (#${issueId})" ->
|
||||
msg(view.Markdown.toHtml(content, r, false, true))) { case (subject, msg) =>
|
||||
recipients(issue) { to =>
|
||||
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)
|
||||
}
|
||||
smtp.fromAddress
|
||||
.map (_ -> smtp.fromName.orNull)
|
||||
.orElse (Some("notifications@gitbucket.com" -> context.loginAccount.get.userName))
|
||||
.foreach { case (address, name) =>
|
||||
email.setFrom(address, name)
|
||||
}
|
||||
email.setCharset("UTF-8")
|
||||
email.setSubject(subject)
|
||||
email.setHtmlMsg(msg)
|
||||
|
||||
email.addTo(to).send
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"Notifications Successful."
|
||||
}
|
||||
f onSuccess {
|
||||
case s => logger.debug(s)
|
||||
}
|
||||
f onFailure {
|
||||
case t => logger.error("Notifications Failed.", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
class MockMailer extends Notifier {
|
||||
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
|
||||
(msg: String => String)(implicit context: Context): Unit = {}
|
||||
package util
|
||||
|
||||
import scala.concurrent._
|
||||
import ExecutionContext.Implicits.global
|
||||
import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail}
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import app.Context
|
||||
import service.{AccountService, RepositoryService, IssuesService, SystemSettingsService}
|
||||
import servlet.Database
|
||||
import SystemSettingsService.Smtp
|
||||
import _root_.util.ControlUtil.defining
|
||||
import model.profile.simple.Session
|
||||
|
||||
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 session: Session, 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(_) filterNot (_.isGroupAccount) foreach (x => notify(x.mailAddress)) )
|
||||
|
||||
}
|
||||
|
||||
object Notifier {
|
||||
// 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}<br/>
|
||||
|--<br/>
|
||||
|<a href="${url}">View it on GitBucket</a>
|
||||
""".stripMargin
|
||||
|
||||
def msgPullRequest(url: String) = (content: String) => s"""
|
||||
|${content}<hr/>
|
||||
|View, comment on, or merge it at:<br/>
|
||||
|<a href="${url}">${url}</a>
|
||||
""".stripMargin
|
||||
|
||||
def msgComment(url: String) = (content: String) => s"""
|
||||
|${content}<br/>
|
||||
|--<br/>
|
||||
|<a href="${url}">View it on GitBucket</a>
|
||||
""".stripMargin
|
||||
|
||||
def msgStatus(url: String) = (content: String) => s"""
|
||||
|${content} <a href="${url}">#${url split('/') last}</a>
|
||||
""".stripMargin
|
||||
}
|
||||
|
||||
class Mailer(private val smtp: Smtp) extends Notifier {
|
||||
private val logger = LoggerFactory.getLogger(classOf[Mailer])
|
||||
|
||||
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
|
||||
(msg: String => String)(implicit context: Context) = {
|
||||
val database = Database(context.request.getServletContext)
|
||||
|
||||
val f = future {
|
||||
// TODO Can we use the Database Session in other than Transaction Filter?
|
||||
database withSession { implicit session =>
|
||||
getIssue(r.owner, r.name, issueId.toString) foreach { issue =>
|
||||
defining(
|
||||
s"[${r.name}] ${issue.title} (#${issueId})" ->
|
||||
msg(view.Markdown.toHtml(content, r, false, true))) { case (subject, msg) =>
|
||||
recipients(issue) { to =>
|
||||
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)
|
||||
}
|
||||
smtp.fromAddress
|
||||
.map (_ -> smtp.fromName.orNull)
|
||||
.orElse (Some("notifications@gitbucket.com" -> context.loginAccount.get.userName))
|
||||
.foreach { case (address, name) =>
|
||||
email.setFrom(address, name)
|
||||
}
|
||||
email.setCharset("UTF-8")
|
||||
email.setSubject(subject)
|
||||
email.setHtmlMsg(msg)
|
||||
|
||||
email.addTo(to).send
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"Notifications Successful."
|
||||
}
|
||||
f onSuccess {
|
||||
case s => logger.debug(s)
|
||||
}
|
||||
f onFailure {
|
||||
case t => logger.error("Notifications Failed.", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
class MockMailer extends Notifier {
|
||||
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
|
||||
(msg: String => String)(implicit context: Context): Unit = {}
|
||||
}
|
||||
Reference in New Issue
Block a user