Fix CRLF to LF

This commit is contained in:
Naoki Takezoe
2014-07-06 13:19:27 +09:00
parent 32897c36f9
commit 606cd83f44
10 changed files with 1087 additions and 1087 deletions

View File

@@ -1,86 +1,86 @@
package app package app
import util._ import util._
import util.Implicits._ import util.Implicits._
import service._ import service._
import jp.sf.amateras.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
class IndexController extends IndexControllerBase class IndexController extends IndexControllerBase
with RepositoryService with ActivityService with AccountService with UsersAuthenticator with RepositoryService with ActivityService with AccountService with UsersAuthenticator
trait IndexControllerBase extends ControllerBase { trait IndexControllerBase extends ControllerBase {
self: RepositoryService with ActivityService with AccountService with UsersAuthenticator => self: RepositoryService with ActivityService with AccountService with UsersAuthenticator =>
case class SignInForm(userName: String, password: String) case class SignInForm(userName: String, password: String)
val form = mapping( val form = mapping(
"userName" -> trim(label("Username", text(required))), "userName" -> trim(label("Username", text(required))),
"password" -> trim(label("Password", text(required))) "password" -> trim(label("Password", text(required)))
)(SignInForm.apply) )(SignInForm.apply)
get("/"){ get("/"){
val loginAccount = context.loginAccount val loginAccount = context.loginAccount
html.index(getRecentActivities(), html.index(getRecentActivities(),
getVisibleRepositories(loginAccount, context.baseUrl), getVisibleRepositories(loginAccount, context.baseUrl),
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl) }.getOrElse(Nil) loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl) }.getOrElse(Nil)
) )
} }
get("/signin"){ get("/signin"){
val redirect = params.get("redirect") val redirect = params.get("redirect")
if(redirect.isDefined && redirect.get.startsWith("/")){ if(redirect.isDefined && redirect.get.startsWith("/")){
flash += Keys.Flash.Redirect -> redirect.get flash += Keys.Flash.Redirect -> redirect.get
} }
html.signin() html.signin()
} }
post("/signin", form){ form => post("/signin", form){ form =>
authenticate(context.settings, form.userName, form.password) match { authenticate(context.settings, form.userName, form.password) match {
case Some(account) => signin(account) case Some(account) => signin(account)
case None => redirect("/signin") case None => redirect("/signin")
} }
} }
get("/signout"){ get("/signout"){
session.invalidate session.invalidate
redirect("/") redirect("/")
} }
get("/activities.atom"){ get("/activities.atom"){
contentType = "application/atom+xml; type=feed" contentType = "application/atom+xml; type=feed"
helper.xml.feed(getRecentActivities()) helper.xml.feed(getRecentActivities())
} }
/** /**
* Set account information into HttpSession and redirect. * Set account information into HttpSession and redirect.
*/ */
private def signin(account: model.Account) = { private def signin(account: model.Account) = {
session.setAttribute(Keys.Session.LoginAccount, account) session.setAttribute(Keys.Session.LoginAccount, account)
updateLastLoginDate(account.userName) updateLastLoginDate(account.userName)
flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl => flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl =>
if(redirectUrl.stripSuffix("/") == request.getContextPath){ if(redirectUrl.stripSuffix("/") == request.getContextPath){
redirect("/") redirect("/")
} else { } else {
redirect(redirectUrl) redirect(redirectUrl)
} }
}.getOrElse { }.getOrElse {
redirect("/") redirect("/")
} }
} }
/** /**
* JSON API for collaborator completion. * JSON API for collaborator completion.
* *
* TODO Move to other controller? * TODO Move to other controller?
*/ */
get("/_user/proposals")(usersOnly { get("/_user/proposals")(usersOnly {
contentType = formats("json") contentType = formats("json")
org.json4s.jackson.Serialization.write( org.json4s.jackson.Serialization.write(
Map("options" -> getAllUsers().filter(!_.isGroupAccount).map(_.userName).toArray) Map("options" -> getAllUsers().filter(!_.isGroupAccount).map(_.userName).toArray)
) )
}) })
} }

View File

@@ -1,267 +1,267 @@
package app package app
import service._ import service._
import util.Directory._ import util.Directory._
import util.ControlUtil._ import util.ControlUtil._
import util.Implicits._ import util.Implicits._
import util.{UsersAuthenticator, OwnerAuthenticator} import util.{UsersAuthenticator, OwnerAuthenticator}
import util.JGitUtil.CommitInfo import util.JGitUtil.CommitInfo
import jp.sf.amateras.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
import service.WebHookService.WebHookPayload import service.WebHookService.WebHookPayload
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
class RepositorySettingsController extends RepositorySettingsControllerBase class RepositorySettingsController extends RepositorySettingsControllerBase
with RepositoryService with AccountService with WebHookService with RepositoryService with AccountService with WebHookService
with OwnerAuthenticator with UsersAuthenticator with OwnerAuthenticator with UsersAuthenticator
trait RepositorySettingsControllerBase extends ControllerBase { trait RepositorySettingsControllerBase extends ControllerBase {
self: RepositoryService with AccountService with WebHookService self: RepositoryService with AccountService with WebHookService
with OwnerAuthenticator with UsersAuthenticator => with OwnerAuthenticator with UsersAuthenticator =>
// for repository options // for repository options
case class OptionsForm(repositoryName: String, description: Option[String], defaultBranch: String, isPrivate: Boolean) case class OptionsForm(repositoryName: String, description: Option[String], defaultBranch: String, isPrivate: Boolean)
val optionsForm = mapping( val optionsForm = mapping(
"repositoryName" -> trim(label("Description" , text(required, maxlength(40), identifier, renameRepositoryName))), "repositoryName" -> trim(label("Description" , text(required, maxlength(40), identifier, renameRepositoryName))),
"description" -> trim(label("Description" , optional(text()))), "description" -> trim(label("Description" , optional(text()))),
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))), "defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
"isPrivate" -> trim(label("Repository Type", boolean())) "isPrivate" -> trim(label("Repository Type", boolean()))
)(OptionsForm.apply) )(OptionsForm.apply)
// for collaborator addition // for collaborator addition
case class CollaboratorForm(userName: String) case class CollaboratorForm(userName: String)
val collaboratorForm = mapping( val collaboratorForm = mapping(
"userName" -> trim(label("Username", text(required, collaborator))) "userName" -> trim(label("Username", text(required, collaborator)))
)(CollaboratorForm.apply) )(CollaboratorForm.apply)
// for web hook url addition // for web hook url addition
case class WebHookForm(url: String) case class WebHookForm(url: String)
val webHookForm = mapping( val webHookForm = mapping(
"url" -> trim(label("url", text(required, webHook))) "url" -> trim(label("url", text(required, webHook)))
)(WebHookForm.apply) )(WebHookForm.apply)
// for transfer ownership // for transfer ownership
case class TransferOwnerShipForm(newOwner: String) case class TransferOwnerShipForm(newOwner: String)
val transferForm = mapping( val transferForm = mapping(
"newOwner" -> trim(label("New owner", text(required, transferUser))) "newOwner" -> trim(label("New owner", text(required, transferUser)))
)(TransferOwnerShipForm.apply) )(TransferOwnerShipForm.apply)
/** /**
* Redirect to the Options page. * Redirect to the Options page.
*/ */
get("/:owner/:repository/settings")(ownerOnly { repository => get("/:owner/:repository/settings")(ownerOnly { repository =>
redirect(s"/${repository.owner}/${repository.name}/settings/options") redirect(s"/${repository.owner}/${repository.name}/settings/options")
}) })
/** /**
* Display the Options page. * Display the Options page.
*/ */
get("/:owner/:repository/settings/options")(ownerOnly { get("/:owner/:repository/settings/options")(ownerOnly {
settings.html.options(_, flash.get("info")) settings.html.options(_, flash.get("info"))
}) })
/** /**
* Save the repository options. * Save the repository options.
*/ */
post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
saveRepositoryOptions( saveRepositoryOptions(
repository.owner, repository.owner,
repository.name, repository.name,
form.description, form.description,
if(repository.branchList.isEmpty) "master" else form.defaultBranch, if(repository.branchList.isEmpty) "master" else form.defaultBranch,
repository.repository.parentUserName.map { _ => repository.repository.parentUserName.map { _ =>
repository.repository.isPrivate repository.repository.isPrivate
} getOrElse form.isPrivate } getOrElse form.isPrivate
) )
// Change repository name // Change repository name
if(repository.name != form.repositoryName){ if(repository.name != form.repositoryName){
// Update database // Update database
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName) renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
// Move git repository // Move git repository
defining(getRepositoryDir(repository.owner, repository.name)){ dir => defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
FileUtils.moveDirectory(dir, getRepositoryDir(repository.owner, form.repositoryName)) FileUtils.moveDirectory(dir, getRepositoryDir(repository.owner, form.repositoryName))
} }
// Move wiki repository // Move wiki repository
defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir => defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName)) FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName))
} }
} }
flash += "info" -> "Repository settings has been updated." flash += "info" -> "Repository settings has been updated."
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options") redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
}) })
/** /**
* Display the Collaborators page. * Display the Collaborators page.
*/ */
get("/:owner/:repository/settings/collaborators")(ownerOnly { repository => get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
settings.html.collaborators( settings.html.collaborators(
getCollaborators(repository.owner, repository.name), getCollaborators(repository.owner, repository.name),
getAccountByUserName(repository.owner).get.isGroupAccount, getAccountByUserName(repository.owner).get.isGroupAccount,
repository) repository)
}) })
/** /**
* Add the collaborator. * Add the collaborator.
*/ */
post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) =>
if(!getAccountByUserName(repository.owner).get.isGroupAccount){ if(!getAccountByUserName(repository.owner).get.isGroupAccount){
addCollaborator(repository.owner, repository.name, form.userName) addCollaborator(repository.owner, repository.name, form.userName)
} }
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
}) })
/** /**
* Add the collaborator. * Add the collaborator.
*/ */
get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository => get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository =>
if(!getAccountByUserName(repository.owner).get.isGroupAccount){ if(!getAccountByUserName(repository.owner).get.isGroupAccount){
removeCollaborator(repository.owner, repository.name, params("name")) removeCollaborator(repository.owner, repository.name, params("name"))
} }
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
}) })
/** /**
* Display the web hook page. * Display the web hook page.
*/ */
get("/:owner/:repository/settings/hooks")(ownerOnly { repository => get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
settings.html.hooks(getWebHookURLs(repository.owner, repository.name), repository, flash.get("info")) settings.html.hooks(getWebHookURLs(repository.owner, repository.name), repository, flash.get("info"))
}) })
/** /**
* Add the web hook URL. * Add the web hook URL.
*/ */
post("/:owner/:repository/settings/hooks/add", webHookForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/hooks/add", webHookForm)(ownerOnly { (form, repository) =>
addWebHookURL(repository.owner, repository.name, form.url) addWebHookURL(repository.owner, repository.name, form.url)
redirect(s"/${repository.owner}/${repository.name}/settings/hooks") redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
}) })
/** /**
* Delete the web hook URL. * Delete the web hook URL.
*/ */
get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository => get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository =>
deleteWebHookURL(repository.owner, repository.name, params("url")) deleteWebHookURL(repository.owner, repository.name, params("url"))
redirect(s"/${repository.owner}/${repository.name}/settings/hooks") redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
}) })
/** /**
* Send the test request to registered web hook URLs. * Send the test request to registered web hook URLs.
*/ */
get("/:owner/:repository/settings/hooks/test")(ownerOnly { repository => get("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
val commits = git.log val commits = git.log
.add(git.getRepository.resolve(repository.repository.defaultBranch)) .add(git.getRepository.resolve(repository.repository.defaultBranch))
.setMaxCount(3) .setMaxCount(3)
.call.iterator.asScala.map(new CommitInfo(_)) .call.iterator.asScala.map(new CommitInfo(_))
getWebHookURLs(repository.owner, repository.name) match { getWebHookURLs(repository.owner, repository.name) match {
case webHookURLs if(webHookURLs.nonEmpty) => case webHookURLs if(webHookURLs.nonEmpty) =>
for(ownerAccount <- getAccountByUserName(repository.owner)){ for(ownerAccount <- getAccountByUserName(repository.owner)){
callWebHook(repository.owner, repository.name, webHookURLs, callWebHook(repository.owner, repository.name, webHookURLs,
WebHookPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount)) WebHookPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount))
} }
case _ => case _ =>
} }
flash += "info" -> "Test payload deployed!" flash += "info" -> "Test payload deployed!"
} }
redirect(s"/${repository.owner}/${repository.name}/settings/hooks") redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
}) })
/** /**
* Display the danger zone. * Display the danger zone.
*/ */
get("/:owner/:repository/settings/danger")(ownerOnly { get("/:owner/:repository/settings/danger")(ownerOnly {
settings.html.danger(_) settings.html.danger(_)
}) })
/** /**
* Transfer repository ownership. * Transfer repository ownership.
*/ */
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
// Change repository owner // Change repository owner
if(repository.owner != form.newOwner){ if(repository.owner != form.newOwner){
// Update database // Update database
renameRepository(repository.owner, repository.name, form.newOwner, repository.name) renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
// Move git repository // Move git repository
defining(getRepositoryDir(repository.owner, repository.name)){ dir => defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
FileUtils.moveDirectory(dir, getRepositoryDir(form.newOwner, repository.name)) FileUtils.moveDirectory(dir, getRepositoryDir(form.newOwner, repository.name))
} }
// Move wiki repository // Move wiki repository
defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir => defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
FileUtils.moveDirectory(dir, getWikiRepositoryDir(form.newOwner, repository.name)) FileUtils.moveDirectory(dir, getWikiRepositoryDir(form.newOwner, repository.name))
} }
} }
redirect(s"/${form.newOwner}/${repository.name}") redirect(s"/${form.newOwner}/${repository.name}")
}) })
/** /**
* Delete the repository. * Delete the repository.
*/ */
post("/:owner/:repository/settings/delete")(ownerOnly { repository => post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
deleteRepository(repository.owner, repository.name) deleteRepository(repository.owner, repository.name)
FileUtils.deleteDirectory(getRepositoryDir(repository.owner, repository.name)) FileUtils.deleteDirectory(getRepositoryDir(repository.owner, repository.name))
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name)) FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name))
FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name)) FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name))
redirect(s"/${repository.owner}") redirect(s"/${repository.owner}")
}) })
/** /**
* Provides duplication check for web hook url. * Provides duplication check for web hook url.
*/ */
private def webHook: Constraint = new Constraint(){ private def webHook: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] = 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.") getWebHookURLs(params("owner"), params("repository")).map(_.url).find(_ == value).map(_ => "URL had been registered already.")
} }
/** /**
* Provides Constraint to validate the collaborator name. * Provides Constraint to validate the collaborator name.
*/ */
private def collaborator: Constraint = new Constraint(){ private def collaborator: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] = override def validate(name: String, value: String, messages: Messages): Option[String] =
getAccountByUserName(value) match { getAccountByUserName(value) match {
case None => Some("User does not exist.") case None => Some("User does not exist.")
case Some(x) if(x.isGroupAccount) case Some(x) if(x.isGroupAccount)
=> Some("User does not exist.") => Some("User does not exist.")
case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName)) case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
=> Some("User can access this repository already.") => Some("User can access this repository already.")
case _ => None case _ => None
} }
} }
/** /**
* Duplicate check for the rename repository name. * Duplicate check for the rename repository name.
*/ */
private def renameRepositoryName: Constraint = new Constraint(){ private def renameRepositoryName: Constraint = new Constraint(){
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
params.get("repository").filter(_ != value).flatMap { _ => params.get("repository").filter(_ != value).flatMap { _ =>
params.get("owner").flatMap { userName => params.get("owner").flatMap { userName =>
getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.") getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.")
} }
} }
} }
/** /**
* Provides Constraint to validate the repository transfer user. * Provides Constraint to validate the repository transfer user.
*/ */
private def transferUser: Constraint = new Constraint(){ private def transferUser: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] = override def validate(name: String, value: String, messages: Messages): Option[String] =
getAccountByUserName(value) match { getAccountByUserName(value) match {
case None => Some("User does not exist.") case None => Some("User does not exist.")
case Some(x) => if(x.userName == params("owner")){ case Some(x) => if(x.userName == params("owner")){
Some("This is current repository owner.") Some("This is current repository owner.")
} else { } else {
params.get("repository").flatMap { repositoryName => params.get("repository").flatMap { repositoryName =>
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map{ _ => "User already has same repository." } getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map{ _ => "User already has same repository." }
} }
} }
} }
} }
} }

View File

@@ -1,39 +1,39 @@
package model package model
trait AccountComponent { self: Profile => trait AccountComponent { self: Profile =>
import profile.simple._ import profile.simple._
import self._ import self._
lazy val Accounts = TableQuery[Accounts] lazy val Accounts = TableQuery[Accounts]
class Accounts(tag: Tag) extends Table[Account](tag, "ACCOUNT") { class Accounts(tag: Tag) extends Table[Account](tag, "ACCOUNT") {
val userName = column[String]("USER_NAME", O PrimaryKey) val userName = column[String]("USER_NAME", O PrimaryKey)
val fullName = column[String]("FULL_NAME") val fullName = column[String]("FULL_NAME")
val mailAddress = column[String]("MAIL_ADDRESS") val mailAddress = column[String]("MAIL_ADDRESS")
val password = column[String]("PASSWORD") val password = column[String]("PASSWORD")
val isAdmin = column[Boolean]("ADMINISTRATOR") val isAdmin = column[Boolean]("ADMINISTRATOR")
val url = column[String]("URL") val url = column[String]("URL")
val registeredDate = column[java.util.Date]("REGISTERED_DATE") val registeredDate = column[java.util.Date]("REGISTERED_DATE")
val updatedDate = column[java.util.Date]("UPDATED_DATE") val updatedDate = column[java.util.Date]("UPDATED_DATE")
val lastLoginDate = column[java.util.Date]("LAST_LOGIN_DATE") val lastLoginDate = column[java.util.Date]("LAST_LOGIN_DATE")
val image = column[String]("IMAGE") val image = column[String]("IMAGE")
val groupAccount = column[Boolean]("GROUP_ACCOUNT") val groupAccount = column[Boolean]("GROUP_ACCOUNT")
val removed = column[Boolean]("REMOVED") val removed = column[Boolean]("REMOVED")
def * = (userName, fullName, mailAddress, password, isAdmin, url.?, registeredDate, updatedDate, lastLoginDate.?, image.?, groupAccount, removed) <> (Account.tupled, Account.unapply) def * = (userName, fullName, mailAddress, password, isAdmin, url.?, registeredDate, updatedDate, lastLoginDate.?, image.?, groupAccount, removed) <> (Account.tupled, Account.unapply)
} }
case class Account( case class Account(
userName: String, userName: String,
fullName: String, fullName: String,
mailAddress: String, mailAddress: String,
password: String, password: String,
isAdmin: Boolean, isAdmin: Boolean,
url: Option[String], url: Option[String],
registeredDate: java.util.Date, registeredDate: java.util.Date,
updatedDate: java.util.Date, updatedDate: java.util.Date,
lastLoginDate: Option[java.util.Date], lastLoginDate: Option[java.util.Date],
image: Option[String], image: Option[String],
isGroupAccount: Boolean, isGroupAccount: Boolean,
isRemoved: Boolean isRemoved: Boolean
) )
} }

View File

@@ -1,47 +1,47 @@
package model package model
protected[model] trait TemplateComponent { self: Profile => protected[model] trait TemplateComponent { self: Profile =>
import profile.simple._ import profile.simple._
trait BasicTemplate { self: Table[_] => trait BasicTemplate { self: Table[_] =>
val userName = column[String]("USER_NAME") val userName = column[String]("USER_NAME")
val repositoryName = column[String]("REPOSITORY_NAME") val repositoryName = column[String]("REPOSITORY_NAME")
def byRepository(owner: String, repository: String) = def byRepository(owner: String, repository: String) =
(userName is owner.bind) && (repositoryName is repository.bind) (userName is owner.bind) && (repositoryName is repository.bind)
def byRepository(userName: Column[String], repositoryName: Column[String]) = def byRepository(userName: Column[String], repositoryName: Column[String]) =
(this.userName is userName) && (this.repositoryName is repositoryName) (this.userName is userName) && (this.repositoryName is repositoryName)
} }
trait IssueTemplate extends BasicTemplate { self: Table[_] => trait IssueTemplate extends BasicTemplate { self: Table[_] =>
val issueId = column[Int]("ISSUE_ID") val issueId = column[Int]("ISSUE_ID")
def byIssue(owner: String, repository: String, issueId: Int) = def byIssue(owner: String, repository: String, issueId: Int) =
byRepository(owner, repository) && (this.issueId is issueId.bind) byRepository(owner, repository) && (this.issueId is issueId.bind)
def byIssue(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) = def byIssue(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) =
byRepository(userName, repositoryName) && (this.issueId is issueId) byRepository(userName, repositoryName) && (this.issueId is issueId)
} }
trait LabelTemplate extends BasicTemplate { self: Table[_] => trait LabelTemplate extends BasicTemplate { self: Table[_] =>
val labelId = column[Int]("LABEL_ID") val labelId = column[Int]("LABEL_ID")
def byLabel(owner: String, repository: String, labelId: Int) = def byLabel(owner: String, repository: String, labelId: Int) =
byRepository(owner, repository) && (this.labelId is labelId.bind) byRepository(owner, repository) && (this.labelId is labelId.bind)
def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) = def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) =
byRepository(userName, repositoryName) && (this.labelId is labelId) byRepository(userName, repositoryName) && (this.labelId is labelId)
} }
trait MilestoneTemplate extends BasicTemplate { self: Table[_] => trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
val milestoneId = column[Int]("MILESTONE_ID") val milestoneId = column[Int]("MILESTONE_ID")
def byMilestone(owner: String, repository: String, milestoneId: Int) = def byMilestone(owner: String, repository: String, milestoneId: Int) =
byRepository(owner, repository) && (this.milestoneId is milestoneId.bind) byRepository(owner, repository) && (this.milestoneId is milestoneId.bind)
def byMilestone(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) = def byMilestone(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) =
byRepository(userName, repositoryName) && (this.milestoneId is milestoneId) byRepository(userName, repositoryName) && (this.milestoneId is milestoneId)
} }
} }

View File

@@ -1,48 +1,48 @@
package model package model
trait IssueComponent extends TemplateComponent { self: Profile => trait IssueComponent extends TemplateComponent { self: Profile =>
import profile.simple._ import profile.simple._
import self._ import self._
lazy val IssueId = TableQuery[IssueId] lazy val IssueId = TableQuery[IssueId]
lazy val IssueOutline = TableQuery[IssueOutline] lazy val IssueOutline = TableQuery[IssueOutline]
lazy val Issues = TableQuery[Issues] lazy val Issues = TableQuery[Issues]
class IssueId(tag: Tag) extends Table[(String, String, Int)](tag, "ISSUE_ID") with IssueTemplate { class IssueId(tag: Tag) extends Table[(String, String, Int)](tag, "ISSUE_ID") with IssueTemplate {
def * = (userName, repositoryName, issueId) def * = (userName, repositoryName, issueId)
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository) 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 { class IssueOutline(tag: Tag) extends Table[(String, String, Int, Int)](tag, "ISSUE_OUTLINE_VIEW") with IssueTemplate {
val commentCount = column[Int]("COMMENT_COUNT") val commentCount = column[Int]("COMMENT_COUNT")
def * = (userName, repositoryName, issueId, commentCount) def * = (userName, repositoryName, issueId, commentCount)
} }
class Issues(tag: Tag) extends Table[Issue](tag, "ISSUE") with IssueTemplate with MilestoneTemplate { class Issues(tag: Tag) extends Table[Issue](tag, "ISSUE") with IssueTemplate with MilestoneTemplate {
val openedUserName = column[String]("OPENED_USER_NAME") val openedUserName = column[String]("OPENED_USER_NAME")
val assignedUserName = column[String]("ASSIGNED_USER_NAME") val assignedUserName = column[String]("ASSIGNED_USER_NAME")
val title = column[String]("TITLE") val title = column[String]("TITLE")
val content = column[String]("CONTENT") val content = column[String]("CONTENT")
val closed = column[Boolean]("CLOSED") val closed = column[Boolean]("CLOSED")
val registeredDate = column[java.util.Date]("REGISTERED_DATE") val registeredDate = column[java.util.Date]("REGISTERED_DATE")
val updatedDate = column[java.util.Date]("UPDATED_DATE") val updatedDate = column[java.util.Date]("UPDATED_DATE")
val pullRequest = column[Boolean]("PULL_REQUEST") val pullRequest = column[Boolean]("PULL_REQUEST")
def * = (userName, repositoryName, issueId, openedUserName, milestoneId.?, assignedUserName.?, title, content.?, closed, registeredDate, updatedDate, pullRequest) <> (Issue.tupled, Issue.unapply) 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) def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
} }
case class Issue( case class Issue(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
issueId: Int, issueId: Int,
openedUserName: String, openedUserName: String,
milestoneId: Option[Int], milestoneId: Option[Int],
assignedUserName: Option[String], assignedUserName: Option[String],
title: String, title: String,
content: Option[String], content: Option[String],
closed: Boolean, closed: Boolean,
registeredDate: java.util.Date, registeredDate: java.util.Date,
updatedDate: java.util.Date, updatedDate: java.util.Date,
isPullRequest: Boolean) isPullRequest: Boolean)
} }

View File

@@ -1,34 +1,34 @@
package model package model
trait IssueCommentComponent extends TemplateComponent { self: Profile => trait IssueCommentComponent extends TemplateComponent { self: Profile =>
import profile.simple._ import profile.simple._
import self._ import self._
lazy val IssueComments = new TableQuery(tag => new IssueComments(tag)){ lazy val IssueComments = new TableQuery(tag => new IssueComments(tag)){
def autoInc = this returning this.map(_.commentId) def autoInc = this returning this.map(_.commentId)
} }
class IssueComments(tag: Tag) extends Table[IssueComment](tag, "ISSUE_COMMENT") with IssueTemplate { class IssueComments(tag: Tag) extends Table[IssueComment](tag, "ISSUE_COMMENT") with IssueTemplate {
val commentId = column[Int]("COMMENT_ID", O AutoInc) val commentId = column[Int]("COMMENT_ID", O AutoInc)
val action = column[String]("ACTION") val action = column[String]("ACTION")
val commentedUserName = column[String]("COMMENTED_USER_NAME") val commentedUserName = column[String]("COMMENTED_USER_NAME")
val content = column[String]("CONTENT") val content = column[String]("CONTENT")
val registeredDate = column[java.util.Date]("REGISTERED_DATE") val registeredDate = column[java.util.Date]("REGISTERED_DATE")
val updatedDate = column[java.util.Date]("UPDATED_DATE") val updatedDate = column[java.util.Date]("UPDATED_DATE")
def * = (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply) def * = (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply)
def byPrimaryKey(commentId: Int) = this.commentId is commentId.bind def byPrimaryKey(commentId: Int) = this.commentId is commentId.bind
} }
case class IssueComment( case class IssueComment(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
issueId: Int, issueId: Int,
commentId: Int = 0, commentId: Int = 0,
action: String, action: String,
commentedUserName: String, commentedUserName: String,
content: String, content: String,
registeredDate: java.util.Date, registeredDate: java.util.Date,
updatedDate: java.util.Date updatedDate: java.util.Date
) )
} }

View File

@@ -1,22 +1,22 @@
package object model extends { package object model extends {
// TODO // TODO
val profile = slick.driver.H2Driver val profile = slick.driver.H2Driver
} with AccountComponent } with AccountComponent
with ActivityComponent with ActivityComponent
with CollaboratorComponent with CollaboratorComponent
with GroupMemberComponent with GroupMemberComponent
with IssueComponent with IssueComponent
with IssueCommentComponent with IssueCommentComponent
with IssueLabelComponent with IssueLabelComponent
with LabelComponent with LabelComponent
with MilestoneComponent with MilestoneComponent
with PullRequestComponent with PullRequestComponent
with RepositoryComponent with RepositoryComponent
with SshKeyComponent with SshKeyComponent
with WebHookComponent with Profile { with WebHookComponent with Profile {
/** /**
* Returns system date. * Returns system date.
*/ */
def currentDate = new java.util.Date() def currentDate = new java.util.Date()
} }

View File

@@ -1,385 +1,385 @@
package service package service
import scala.slick.jdbc.{StaticQuery => Q} import scala.slick.jdbc.{StaticQuery => Q}
import Q.interpolation import Q.interpolation
import model._ import model._
import profile.simple._ import profile.simple._
import util.Implicits._ import util.Implicits._
import util.StringUtil._ import util.StringUtil._
trait IssuesService { trait IssuesService {
import IssuesService._ import IssuesService._
def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) = def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) =
if (issueId forall (_.isDigit)) if (issueId forall (_.isDigit))
Issues filter (_.byPrimaryKey(owner, repository, issueId.toInt)) firstOption Issues filter (_.byPrimaryKey(owner, repository, issueId.toInt)) firstOption
else None else None
def getComments(owner: String, repository: String, issueId: Int)(implicit s: Session) = def getComments(owner: String, repository: String, issueId: Int)(implicit s: Session) =
IssueComments filter (_.byIssue(owner, repository, issueId)) list IssueComments filter (_.byIssue(owner, repository, issueId)) list
def getComment(owner: String, repository: String, commentId: String)(implicit s: Session) = def getComment(owner: String, repository: String, commentId: String)(implicit s: Session) =
if (commentId forall (_.isDigit)) if (commentId forall (_.isDigit))
IssueComments filter { t => IssueComments filter { t =>
t.byPrimaryKey(commentId.toInt) && t.byRepository(owner, repository) t.byPrimaryKey(commentId.toInt) && t.byRepository(owner, repository)
} firstOption } firstOption
else None else None
def getIssueLabels(owner: String, repository: String, issueId: Int)(implicit s: Session) = def getIssueLabels(owner: String, repository: String, issueId: Int)(implicit s: Session) =
IssueLabels IssueLabels
.innerJoin(Labels).on { (t1, t2) => .innerJoin(Labels).on { (t1, t2) =>
t1.byLabel(t2.userName, t2.repositoryName, t2.labelId) t1.byLabel(t2.userName, t2.repositoryName, t2.labelId)
} }
.filter ( _._1.byIssue(owner, repository, issueId) ) .filter ( _._1.byIssue(owner, repository, issueId) )
.map ( _._2 ) .map ( _._2 )
.list .list
def getIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session) = def getIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session) =
IssueLabels filter (_.byPrimaryKey(owner, repository, issueId, labelId)) firstOption IssueLabels filter (_.byPrimaryKey(owner, repository, issueId, labelId)) firstOption
/** /**
* Returns the count of the search result against issues. * Returns the count of the search result against issues.
* *
* @param condition the search condition * @param condition the search condition
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name) * @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 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 * @param repos Tuple of the repository owner and the repository name
* @return the count of the search result * @return the count of the search result
*/ */
def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean, def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
repos: (String, String)*)(implicit s: Session): Int = repos: (String, String)*)(implicit s: Session): Int =
// TODO check SQL // TODO check SQL
Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first
/** /**
* Returns the Map which contains issue count for each labels. * Returns the Map which contains issue count for each labels.
* *
* @param owner the repository owner * @param owner the repository owner
* @param repository the repository name * @param repository the repository name
* @param condition the search condition * @param condition the search condition
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name) * @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) * @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, def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition,
filterUser: Map[String, String])(implicit s: Session): Map[String, Int] = { filterUser: Map[String, String])(implicit s: Session): Map[String, Int] = {
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), filterUser, false) searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), filterUser, false)
.innerJoin(IssueLabels).on { (t1, t2) => .innerJoin(IssueLabels).on { (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
} }
.innerJoin(Labels).on { case ((t1, t2), t3) => .innerJoin(Labels).on { case ((t1, t2), t3) =>
t2.byLabel(t3.userName, t3.repositoryName, t3.labelId) t2.byLabel(t3.userName, t3.repositoryName, t3.labelId)
} }
.groupBy { case ((t1, t2), t3) => .groupBy { case ((t1, t2), t3) =>
t3.labelName t3.labelName
} }
.map { case (labelName, t) => .map { case (labelName, t) =>
labelName -> t.length labelName -> t.length
} }
.toMap .toMap
} }
/** /**
* Returns list which contains issue count for each repository. * Returns list which contains issue count for each repository.
* If the issue does not exist, its repository is not included in the result. * If the issue does not exist, its repository is not included in the result.
* *
* @param condition the search condition * @param condition the search condition
* @param filterUser the filter user name (key is "all", "assigned" or "created_by", value is the user name) * @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 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 * @param repos Tuple of the repository owner and the repository name
* @return list which contains issue count for each repository * @return list which contains issue count for each repository
*/ */
def countIssueGroupByRepository( def countIssueGroupByRepository(
condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean, condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
repos: (String, String)*)(implicit s: Session): List[(String, String, Int)] = { repos: (String, String)*)(implicit s: Session): List[(String, String, Int)] = {
searchIssueQuery(repos, condition.copy(repo = None), filterUser, onlyPullRequest) searchIssueQuery(repos, condition.copy(repo = None), filterUser, onlyPullRequest)
.groupBy { t => .groupBy { t =>
t.userName -> t.repositoryName t.userName -> t.repositoryName
} }
.map { case (repo, t) => .map { case (repo, t) =>
(repo._1, repo._2, t.length) (repo._1, repo._2, t.length)
} }
.sortBy(_._3 desc) .sortBy(_._3 desc)
.list .list
} }
/** /**
* Returns the search result against issues. * Returns the search result against issues.
* *
* @param condition the search condition * @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 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 onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request.
* @param offset the offset for pagination * @param offset the offset for pagination
* @param limit the limit for pagination * @param limit the limit for pagination
* @param repos Tuple of the repository owner and the repository name * @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) * @return the search result (list of tuples which contain issue, labels and comment count)
*/ */
def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean, def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
offset: Int, limit: Int, repos: (String, String)*) offset: Int, limit: Int, repos: (String, String)*)
(implicit s: Session): List[(Issue, List[Label], Int)] = { (implicit s: Session): List[(Issue, List[Label], Int)] = {
// get issues and comment count and labels // get issues and comment count and labels
searchIssueQuery(repos, condition, filterUser, onlyPullRequest) searchIssueQuery(repos, condition, filterUser, onlyPullRequest)
.innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) } .innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) }
.sortBy { case (t1, t2) => .sortBy { case (t1, t2) =>
(condition.sort match { (condition.sort match {
case "created" => t1.registeredDate case "created" => t1.registeredDate
case "comments" => t2.commentCount case "comments" => t2.commentCount
case "updated" => t1.updatedDate case "updated" => t1.updatedDate
}) match { }) match {
case sort => condition.direction match { case sort => condition.direction match {
case "asc" => sort asc case "asc" => sort asc
case "desc" => sort desc case "desc" => sort desc
} }
} }
} }
.drop(offset).take(limit) .drop(offset).take(limit)
.leftJoin (IssueLabels) .on { case ((t1, t2), t3) => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) } .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) } .leftJoin (Labels) .on { case (((t1, t2), t3), t4) => t3.byLabel(t4.userName, t4.repositoryName, t4.labelId) }
.map { case (((t1, t2), t3), t4) => .map { case (((t1, t2), t3), t4) =>
(t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?) (t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?)
} }
.list .list
.splitWith { (c1, c2) => .splitWith { (c1, c2) =>
c1._1.userName == c2._1.userName && c1._1.userName == c2._1.userName &&
c1._1.repositoryName == c2._1.repositoryName && c1._1.repositoryName == c2._1.repositoryName &&
c1._1.issueId == c2._1.issueId c1._1.issueId == c2._1.issueId
} }
.map { issues => issues.head match { .map { issues => issues.head match {
case (issue, commentCount, _,_,_) => case (issue, commentCount, _,_,_) =>
(issue, (issue,
issues.flatMap { t => t._3.map ( issues.flatMap { t => t._3.map (
Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get) Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get)
)} toList, )} toList,
commentCount) commentCount)
}} toList }} toList
} }
/** /**
* Assembles query for conditional issue searching. * Assembles query for conditional issue searching.
*/ */
private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition, private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition,
filterUser: Map[String, String], onlyPullRequest: Boolean)(implicit s: Session) = filterUser: Map[String, String], onlyPullRequest: Boolean)(implicit s: Session) =
Issues filter { t1 => Issues filter { t1 =>
condition.repo condition.repo
.map { _.split('/') match { case array => Seq(array(0) -> array(1)) } } .map { _.split('/') match { case array => Seq(array(0) -> array(1)) } }
.getOrElse (repos) .getOrElse (repos)
.map { case (owner, repository) => t1.byRepository(owner, repository) } .map { case (owner, repository) => t1.byRepository(owner, repository) }
.foldLeft[Column[Boolean]](false) ( _ || _ ) && .foldLeft[Column[Boolean]](false) ( _ || _ ) &&
(t1.closed is (condition.state == "closed").bind) && (t1.closed is (condition.state == "closed").bind) &&
(t1.milestoneId is condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) && (t1.milestoneId is condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
(t1.milestoneId isNull, condition.milestoneId == Some(None)) && (t1.milestoneId isNull, condition.milestoneId == Some(None)) &&
(t1.assignedUserName is filterUser("assigned").bind, filterUser.get("assigned").isDefined) && (t1.assignedUserName is filterUser("assigned").bind, filterUser.get("assigned").isDefined) &&
(t1.openedUserName is filterUser("created_by").bind, filterUser.get("created_by").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.openedUserName isNot filterUser("not_created_by").bind, filterUser.get("not_created_by").isDefined) &&
(t1.pullRequest is true.bind, onlyPullRequest) && (t1.pullRequest is true.bind, onlyPullRequest) &&
(IssueLabels filter { t2 => (IssueLabels filter { t2 =>
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
(t2.labelId in (t2.labelId in
(Labels filter { t3 => (Labels filter { t3 =>
(t3.byRepository(t1.userName, t1.repositoryName)) && (t3.byRepository(t1.userName, t1.repositoryName)) &&
(t3.labelName inSetBind condition.labels) (t3.labelName inSetBind condition.labels)
} map(_.labelId))) } map(_.labelId)))
} exists, condition.labels.nonEmpty) } exists, condition.labels.nonEmpty)
} }
def createIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String], def createIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String],
assignedUserName: Option[String], milestoneId: Option[Int], assignedUserName: Option[String], milestoneId: Option[Int],
isPullRequest: Boolean = false)(implicit s: Session) = isPullRequest: Boolean = false)(implicit s: Session) =
// next id number // next id number
sql"SELECT ISSUE_ID + 1 FROM ISSUE_ID WHERE USER_NAME = $owner AND REPOSITORY_NAME = $repository FOR UPDATE".as[Int] sql"SELECT ISSUE_ID + 1 FROM ISSUE_ID WHERE USER_NAME = $owner AND REPOSITORY_NAME = $repository FOR UPDATE".as[Int]
.firstOption.filter { id => .firstOption.filter { id =>
Issues insert Issue( Issues insert Issue(
owner, owner,
repository, repository,
id, id,
loginUser, loginUser,
milestoneId, milestoneId,
assignedUserName, assignedUserName,
title, title,
content, content,
false, false,
currentDate, currentDate,
currentDate, currentDate,
isPullRequest) isPullRequest)
// increment issue id // increment issue id
IssueId IssueId
.filter (_.byPrimaryKey(owner, repository)) .filter (_.byPrimaryKey(owner, repository))
.map (_.issueId) .map (_.issueId)
.update (id) > 0 .update (id) > 0
} get } get
def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session) = def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session) =
IssueLabels insert IssueLabel(owner, repository, issueId, labelId) IssueLabels insert IssueLabel(owner, repository, issueId, labelId)
def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session) = def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session) =
IssueLabels filter(_.byPrimaryKey(owner, repository, issueId, labelId)) delete IssueLabels filter(_.byPrimaryKey(owner, repository, issueId, labelId)) delete
def createComment(owner: String, repository: String, loginUser: String, def createComment(owner: String, repository: String, loginUser: String,
issueId: Int, content: String, action: String)(implicit s: Session): Int = issueId: Int, content: String, action: String)(implicit s: Session): Int =
IssueComments.autoInc insert IssueComment( IssueComments.autoInc insert IssueComment(
userName = owner, userName = owner,
repositoryName = repository, repositoryName = repository,
issueId = issueId, issueId = issueId,
action = action, action = action,
commentedUserName = loginUser, commentedUserName = loginUser,
content = content, content = content,
registeredDate = currentDate, registeredDate = currentDate,
updatedDate = currentDate) updatedDate = currentDate)
def updateIssue(owner: String, repository: String, issueId: Int, def updateIssue(owner: String, repository: String, issueId: Int,
title: String, content: Option[String])(implicit s: Session) = title: String, content: Option[String])(implicit s: Session) =
Issues Issues
.filter (_.byPrimaryKey(owner, repository, issueId)) .filter (_.byPrimaryKey(owner, repository, issueId))
.map { t => .map { t =>
(t.title, t.content.?, t.updatedDate) (t.title, t.content.?, t.updatedDate)
} }
.update (title, content, currentDate) .update (title, content, currentDate)
def updateAssignedUserName(owner: String, repository: String, issueId: Int, def updateAssignedUserName(owner: String, repository: String, issueId: Int,
assignedUserName: Option[String])(implicit s: Session) = assignedUserName: Option[String])(implicit s: Session) =
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.assignedUserName?).update (assignedUserName) Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.assignedUserName?).update (assignedUserName)
def updateMilestoneId(owner: String, repository: String, issueId: Int, def updateMilestoneId(owner: String, repository: String, issueId: Int,
milestoneId: Option[Int])(implicit s: Session) = milestoneId: Option[Int])(implicit s: Session) =
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.milestoneId?).update (milestoneId) Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.milestoneId?).update (milestoneId)
def updateComment(commentId: Int, content: String)(implicit s: Session) = def updateComment(commentId: Int, content: String)(implicit s: Session) =
IssueComments IssueComments
.filter (_.byPrimaryKey(commentId)) .filter (_.byPrimaryKey(commentId))
.map { t => .map { t =>
t.content -> t.updatedDate t.content -> t.updatedDate
} }
.update (content, currentDate) .update (content, currentDate)
def deleteComment(commentId: Int)(implicit s: Session) = def deleteComment(commentId: Int)(implicit s: Session) =
IssueComments filter (_.byPrimaryKey(commentId)) delete IssueComments filter (_.byPrimaryKey(commentId)) delete
def updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean)(implicit s: Session) = def updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean)(implicit s: Session) =
Issues Issues
.filter (_.byPrimaryKey(owner, repository, issueId)) .filter (_.byPrimaryKey(owner, repository, issueId))
.map { t => .map { t =>
t.closed -> t.updatedDate t.closed -> t.updatedDate
} }
.update (closed, currentDate) .update (closed, currentDate)
/** /**
* Search issues by keyword. * Search issues by keyword.
* *
* @param owner the repository owner * @param owner the repository owner
* @param repository the repository name * @param repository the repository name
* @param query the keywords separated by whitespace. * @param query the keywords separated by whitespace.
* @return issues with comment count and matched content of issue or comment * @return issues with comment count and matched content of issue or comment
*/ */
def searchIssuesByKeyword(owner: String, repository: String, query: String) def searchIssuesByKeyword(owner: String, repository: String, query: String)
(implicit s: Session): List[(Issue, Int, String)] = { (implicit s: Session): List[(Issue, Int, String)] = {
import slick.driver.JdbcDriver.likeEncode import slick.driver.JdbcDriver.likeEncode
val keywords = splitWords(query.toLowerCase) val keywords = splitWords(query.toLowerCase)
// Search Issue // Search Issue
val issues = Issues val issues = Issues
.innerJoin(IssueOutline).on { case (t1, t2) => .innerJoin(IssueOutline).on { case (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
} }
.filter { case (t1, t2) => .filter { case (t1, t2) =>
keywords.map { keyword => keywords.map { keyword =>
(t1.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) || (t1.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) ||
(t1.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) (t1.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^'))
} .reduceLeft(_ && _) } .reduceLeft(_ && _)
} }
.map { case (t1, t2) => .map { case (t1, t2) =>
(t1, 0, t1.content.?, t2.commentCount) (t1, 0, t1.content.?, t2.commentCount)
} }
// Search IssueComment // Search IssueComment
val comments = IssueComments val comments = IssueComments
.innerJoin(Issues).on { case (t1, t2) => .innerJoin(Issues).on { case (t1, t2) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
} }
.innerJoin(IssueOutline).on { case ((t1, t2), t3) => .innerJoin(IssueOutline).on { case ((t1, t2), t3) =>
t2.byIssue(t3.userName, t3.repositoryName, t3.issueId) t2.byIssue(t3.userName, t3.repositoryName, t3.issueId)
} }
.filter { case ((t1, t2), t3) => .filter { case ((t1, t2), t3) =>
keywords.map { query => keywords.map { query =>
t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^') t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^')
}.reduceLeft(_ && _) }.reduceLeft(_ && _)
} }
.map { case ((t1, t2), t3) => .map { case ((t1, t2), t3) =>
(t2, t1.commentId, t1.content.?, t3.commentCount) (t2, t1.commentId, t1.content.?, t3.commentCount)
} }
issues.union(comments).sortBy { case (issue, commentId, _, _) => issues.union(comments).sortBy { case (issue, commentId, _, _) =>
issue.issueId -> commentId issue.issueId -> commentId
}.list.splitWith { case ((issue1, _, _, _), (issue2, _, _, _)) => }.list.splitWith { case ((issue1, _, _, _), (issue2, _, _, _)) =>
issue1.issueId == issue2.issueId issue1.issueId == issue2.issueId
}.map { _.head match { }.map { _.head match {
case (issue, _, content, commentCount) => (issue, commentCount, content.getOrElse("")) case (issue, _, content, commentCount) => (issue, commentCount, content.getOrElse(""))
} }
}.toList }.toList
} }
def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String)(implicit s: Session) = { def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String)(implicit s: Session) = {
extractCloseId(message).foreach { issueId => extractCloseId(message).foreach { issueId =>
for(issue <- getIssue(owner, repository, issueId) if !issue.closed){ for(issue <- getIssue(owner, repository, issueId) if !issue.closed){
createComment(owner, repository, userName, issue.issueId, "Close", "close") createComment(owner, repository, userName, issue.issueId, "Close", "close")
updateClosed(owner, repository, issue.issueId, true) updateClosed(owner, repository, issue.issueId, true)
} }
} }
} }
} }
object IssuesService { object IssuesService {
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
val IssueLimit = 30 val IssueLimit = 30
case class IssueSearchCondition( case class IssueSearchCondition(
labels: Set[String] = Set.empty, labels: Set[String] = Set.empty,
milestoneId: Option[Option[Int]] = None, milestoneId: Option[Option[Int]] = None,
repo: Option[String] = None, repo: Option[String] = None,
state: String = "open", state: String = "open",
sort: String = "created", sort: String = "created",
direction: String = "desc"){ direction: String = "desc"){
def toURL: String = def toURL: String =
"?" + List( "?" + List(
if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))), if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
milestoneId.map { id => "milestone=" + (id match { milestoneId.map { id => "milestone=" + (id match {
case Some(x) => x.toString case Some(x) => x.toString
case None => "none" case None => "none"
})}, })},
repo.map("for=" + urlEncode(_)), repo.map("for=" + urlEncode(_)),
Some("state=" + urlEncode(state)), Some("state=" + urlEncode(state)),
Some("sort=" + urlEncode(sort)), Some("sort=" + urlEncode(sort)),
Some("direction=" + urlEncode(direction))).flatten.mkString("&") Some("direction=" + urlEncode(direction))).flatten.mkString("&")
} }
object IssueSearchCondition { object IssueSearchCondition {
private def param(request: HttpServletRequest, name: String, allow: Seq[String] = Nil): Option[String] = { private def param(request: HttpServletRequest, name: String, allow: Seq[String] = Nil): Option[String] = {
val value = request.getParameter(name) val value = request.getParameter(name)
if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value) if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
} }
def apply(request: HttpServletRequest): IssueSearchCondition = def apply(request: HttpServletRequest): IssueSearchCondition =
IssueSearchCondition( IssueSearchCondition(
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty), param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
param(request, "milestone").map{ param(request, "milestone").map{
case "none" => None case "none" => None
case x => x.toIntOpt case x => x.toIntOpt
}, },
param(request, "for"), param(request, "for"),
param(request, "state", Seq("open", "closed")).getOrElse("open"), param(request, "state", Seq("open", "closed")).getOrElse("open"),
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"), param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
param(request, "direction", Seq("asc", "desc")).getOrElse("desc")) param(request, "direction", Seq("asc", "desc")).getOrElse("desc"))
def page(request: HttpServletRequest) = try { def page(request: HttpServletRequest) = try {
val i = param(request, "page").getOrElse("1").toInt val i = param(request, "page").getOrElse("1").toInt
if(i <= 0) 1 else i if(i <= 0) 1 else i
} catch { } catch {
case e: NumberFormatException => 1 case e: NumberFormatException => 1
} }
} }
} }

View File

@@ -1,44 +1,44 @@
package servlet package servlet
import javax.servlet._ import javax.servlet._
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
import util.Keys import util.Keys
/** /**
* Controls the transaction with the open session in view pattern. * Controls the transaction with the open session in view pattern.
*/ */
class TransactionFilter extends Filter { class TransactionFilter extends Filter {
private val logger = LoggerFactory.getLogger(classOf[TransactionFilter]) private val logger = LoggerFactory.getLogger(classOf[TransactionFilter])
def init(config: FilterConfig) = {} def init(config: FilterConfig) = {}
def destroy(): Unit = {} def destroy(): Unit = {}
def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = { def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
if(req.asInstanceOf[HttpServletRequest].getRequestURI().startsWith("/assets/")){ if(req.asInstanceOf[HttpServletRequest].getRequestURI().startsWith("/assets/")){
// assets don't need transaction // assets don't need transaction
chain.doFilter(req, res) chain.doFilter(req, res)
} else { } else {
Database(req.getServletContext) withTransaction { session => Database(req.getServletContext) withTransaction { session =>
logger.debug("begin transaction") logger.debug("begin transaction")
req.setAttribute(Keys.Request.DBSession, session) req.setAttribute(Keys.Request.DBSession, session)
chain.doFilter(req, res) chain.doFilter(req, res)
logger.debug("end transaction") logger.debug("end transaction")
} }
} }
} }
} }
object Database { object Database {
def apply(context: ServletContext): slick.jdbc.JdbcBackend.Database = def apply(context: ServletContext): slick.jdbc.JdbcBackend.Database =
slick.jdbc.JdbcBackend.Database.forURL(context.getInitParameter("db.url"), slick.jdbc.JdbcBackend.Database.forURL(context.getInitParameter("db.url"),
context.getInitParameter("db.user"), context.getInitParameter("db.user"),
context.getInitParameter("db.password")) context.getInitParameter("db.password"))
def getSession(req: ServletRequest): slick.jdbc.JdbcBackend#Session = def getSession(req: ServletRequest): slick.jdbc.JdbcBackend#Session =
req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session] req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
} }

View File

@@ -1,117 +1,117 @@
package util package util
import scala.concurrent._ import scala.concurrent._
import ExecutionContext.Implicits.global import ExecutionContext.Implicits.global
import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail} import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail}
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import app.Context import app.Context
import service.{AccountService, RepositoryService, IssuesService, SystemSettingsService} import service.{AccountService, RepositoryService, IssuesService, SystemSettingsService}
import servlet.Database import servlet.Database
import SystemSettingsService.Smtp import SystemSettingsService.Smtp
import _root_.util.ControlUtil.defining import _root_.util.ControlUtil.defining
import model.profile.simple.Session import model.profile.simple.Session
trait Notifier extends RepositoryService with AccountService with IssuesService { trait Notifier extends RepositoryService with AccountService with IssuesService {
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String) def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
(msg: String => String)(implicit context: Context): Unit (msg: String => String)(implicit context: Context): Unit
protected def recipients(issue: model.Issue)(notify: String => Unit)(implicit session: Session, context: Context) = protected def recipients(issue: model.Issue)(notify: String => Unit)(implicit session: Session, context: Context) =
( (
// individual repository's owner // individual repository's owner
issue.userName :: issue.userName ::
// collaborators // collaborators
getCollaborators(issue.userName, issue.repositoryName) ::: getCollaborators(issue.userName, issue.repositoryName) :::
// participants // participants
issue.openedUserName :: issue.openedUserName ::
getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName) getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName)
) )
.distinct .distinct
.withFilter ( _ != context.loginAccount.get.userName ) // the operation in person is excluded .withFilter ( _ != context.loginAccount.get.userName ) // the operation in person is excluded
.foreach ( getAccountByUserName(_) filterNot (_.isGroupAccount) foreach (x => notify(x.mailAddress)) ) .foreach ( getAccountByUserName(_) filterNot (_.isGroupAccount) foreach (x => notify(x.mailAddress)) )
} }
object Notifier { object Notifier {
// TODO We want to be able to switch to mock. // TODO We want to be able to switch to mock.
def apply(): Notifier = new SystemSettingsService {}.loadSystemSettings match { def apply(): Notifier = new SystemSettingsService {}.loadSystemSettings match {
case settings if settings.notification => new Mailer(settings.smtp.get) case settings if settings.notification => new Mailer(settings.smtp.get)
case _ => new MockMailer case _ => new MockMailer
} }
def msgIssue(url: String) = (content: String) => s""" def msgIssue(url: String) = (content: String) => s"""
|${content}<br/> |${content}<br/>
|--<br/> |--<br/>
|<a href="${url}">View it on GitBucket</a> |<a href="${url}">View it on GitBucket</a>
""".stripMargin """.stripMargin
def msgPullRequest(url: String) = (content: String) => s""" def msgPullRequest(url: String) = (content: String) => s"""
|${content}<hr/> |${content}<hr/>
|View, comment on, or merge it at:<br/> |View, comment on, or merge it at:<br/>
|<a href="${url}">${url}</a> |<a href="${url}">${url}</a>
""".stripMargin """.stripMargin
def msgComment(url: String) = (content: String) => s""" def msgComment(url: String) = (content: String) => s"""
|${content}<br/> |${content}<br/>
|--<br/> |--<br/>
|<a href="${url}">View it on GitBucket</a> |<a href="${url}">View it on GitBucket</a>
""".stripMargin """.stripMargin
def msgStatus(url: String) = (content: String) => s""" def msgStatus(url: String) = (content: String) => s"""
|${content} <a href="${url}">#${url split('/') last}</a> |${content} <a href="${url}">#${url split('/') last}</a>
""".stripMargin """.stripMargin
} }
class Mailer(private val smtp: Smtp) extends Notifier { class Mailer(private val smtp: Smtp) extends Notifier {
private val logger = LoggerFactory.getLogger(classOf[Mailer]) private val logger = LoggerFactory.getLogger(classOf[Mailer])
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String) def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
(msg: String => String)(implicit context: Context) = { (msg: String => String)(implicit context: Context) = {
val database = Database(context.request.getServletContext) val database = Database(context.request.getServletContext)
val f = future { val f = future {
// TODO Can we use the Database Session in other than Transaction Filter? // TODO Can we use the Database Session in other than Transaction Filter?
database withSession { implicit session => database withSession { implicit session =>
getIssue(r.owner, r.name, issueId.toString) foreach { issue => getIssue(r.owner, r.name, issueId.toString) foreach { issue =>
defining( defining(
s"[${r.name}] ${issue.title} (#${issueId})" -> s"[${r.name}] ${issue.title} (#${issueId})" ->
msg(view.Markdown.toHtml(content, r, false, true))) { case (subject, msg) => msg(view.Markdown.toHtml(content, r, false, true))) { case (subject, msg) =>
recipients(issue) { to => recipients(issue) { to =>
val email = new HtmlEmail val email = new HtmlEmail
email.setHostName(smtp.host) email.setHostName(smtp.host)
email.setSmtpPort(smtp.port.get) email.setSmtpPort(smtp.port.get)
smtp.user.foreach { user => smtp.user.foreach { user =>
email.setAuthenticator(new DefaultAuthenticator(user, smtp.password.getOrElse(""))) email.setAuthenticator(new DefaultAuthenticator(user, smtp.password.getOrElse("")))
} }
smtp.ssl.foreach { ssl => smtp.ssl.foreach { ssl =>
email.setSSLOnConnect(ssl) email.setSSLOnConnect(ssl)
} }
smtp.fromAddress smtp.fromAddress
.map (_ -> smtp.fromName.orNull) .map (_ -> smtp.fromName.orNull)
.orElse (Some("notifications@gitbucket.com" -> context.loginAccount.get.userName)) .orElse (Some("notifications@gitbucket.com" -> context.loginAccount.get.userName))
.foreach { case (address, name) => .foreach { case (address, name) =>
email.setFrom(address, name) email.setFrom(address, name)
} }
email.setCharset("UTF-8") email.setCharset("UTF-8")
email.setSubject(subject) email.setSubject(subject)
email.setHtmlMsg(msg) email.setHtmlMsg(msg)
email.addTo(to).send email.addTo(to).send
} }
} }
} }
} }
"Notifications Successful." "Notifications Successful."
} }
f onSuccess { f onSuccess {
case s => logger.debug(s) case s => logger.debug(s)
} }
f onFailure { f onFailure {
case t => logger.error("Notifications Failed.", t) case t => logger.error("Notifications Failed.", t)
} }
} }
} }
class MockMailer extends Notifier { class MockMailer extends Notifier {
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String) def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
(msg: String => String)(implicit context: Context): Unit = {} (msg: String => String)(implicit context: Context): Unit = {}
} }