") + var text: String = node.getText + while (text.charAt(0) == '\n') { + printer.print("") + } +} + +class GitBucketHtmlSerializer( + markdown: String, + repository: RepositoryService.RepositoryInfo, + enableWikiLink: Boolean, + enableRefsLink: Boolean, + enableTaskList: Boolean, + hasWritePermission: Boolean, + pages: List[String] + )(implicit val context: Context) extends ToHtmlSerializer( + new GitBucketLinkRender(context, repository, enableWikiLink, pages), + Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava + ) with LinkConverter with RequestCache { + + override protected def printImageTag(imageNode: SuperNode, url: String): Unit = { + printer.print("") + .print("
") + text = text.substring(1) + } + printer.printEncoded(text) + printer.print("
${value.body.trim.split("\n").map(_.trim).mkString("\n")}") + + /** + * Implicit conversion to add mkHtml() to Seq[Html]. + */ + implicit class RichHtmlSeq(seq: Seq[Html]) { + def mkHtml(separator: String) = Html(seq.mkString(separator)) + def mkHtml(separator: scala.xml.Elem) = Html(seq.mkString(separator.toString)) + } + + def commitStateIcon(state: CommitState) = Html(state match { + case CommitState.PENDING => "●" + case CommitState.SUCCESS => "✔" + case CommitState.ERROR => "×" + case CommitState.FAILURE => "×" + }) + + def commitStateText(state: CommitState, commitId:String) = state match { + case CommitState.PENDING => "Waiting to hear about "+commitId.substring(0,8) + case CommitState.SUCCESS => "All is well" + case CommitState.ERROR => "Failed" + case CommitState.FAILURE => "Failed" + } +} diff --git a/src/main/scala/model/AccessToken.scala b/src/main/scala/model/AccessToken.scala deleted file mode 100644 index 2695c2f..0000000 --- a/src/main/scala/model/AccessToken.scala +++ /dev/null @@ -1,20 +0,0 @@ -package model - -trait AccessTokenComponent { self: Profile => - import profile.simple._ - lazy val AccessTokens = TableQuery[AccessTokens] - - class AccessTokens(tag: Tag) extends Table[AccessToken](tag, "ACCESS_TOKEN") { - val accessTokenId = column[Int]("ACCESS_TOKEN_ID", O AutoInc) - val userName = column[String]("USER_NAME") - val tokenHash = column[String]("TOKEN_HASH") - val note = column[String]("NOTE") - def * = (accessTokenId, userName, tokenHash, note) <> (AccessToken.tupled, AccessToken.unapply) - } -} -case class AccessToken( - accessTokenId: Int = 0, - userName: String, - tokenHash: String, - note: String -) diff --git a/src/main/scala/model/Account.scala b/src/main/scala/model/Account.scala deleted file mode 100644 index 012c559..0000000 --- a/src/main/scala/model/Account.scala +++ /dev/null @@ -1,39 +0,0 @@ -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 -) diff --git a/src/main/scala/model/Activity.scala b/src/main/scala/model/Activity.scala deleted file mode 100644 index 8e3960e..0000000 --- a/src/main/scala/model/Activity.scala +++ /dev/null @@ -1,29 +0,0 @@ -package model - -trait ActivityComponent extends TemplateComponent { self: Profile => - import profile.simple._ - import self._ - - lazy val Activities = TableQuery[Activities] - - class Activities(tag: Tag) extends Table[Activity](tag, "ACTIVITY") with BasicTemplate { - val activityId = column[Int]("ACTIVITY_ID", O AutoInc) - val activityUserName = column[String]("ACTIVITY_USER_NAME") - val activityType = column[String]("ACTIVITY_TYPE") - val message = column[String]("MESSAGE") - val additionalInfo = column[String]("ADDITIONAL_INFO") - val activityDate = column[java.util.Date]("ACTIVITY_DATE") - def * = (userName, repositoryName, activityUserName, activityType, message, additionalInfo.?, activityDate, activityId) <> (Activity.tupled, Activity.unapply) - } -} - -case class Activity( - userName: String, - repositoryName: String, - activityUserName: String, - activityType: String, - message: String, - additionalInfo: Option[String], - activityDate: java.util.Date, - activityId: Int = 0 -) diff --git a/src/main/scala/model/BasicTemplate.scala b/src/main/scala/model/BasicTemplate.scala deleted file mode 100644 index 1d012c1..0000000 --- a/src/main/scala/model/BasicTemplate.scala +++ /dev/null @@ -1,57 +0,0 @@ -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 === owner.bind) && (repositoryName === repository.bind) - - def byRepository(userName: Column[String], repositoryName: Column[String]) = - (this.userName === userName) && (this.repositoryName === 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 === issueId.bind) - - def byIssue(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) = - byRepository(userName, repositoryName) && (this.issueId === 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 === labelId.bind) - - def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) = - byRepository(userName, repositoryName) && (this.labelId === 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 === milestoneId.bind) - - def byMilestone(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) = - byRepository(userName, repositoryName) && (this.milestoneId === milestoneId) - } - - trait CommitTemplate extends BasicTemplate { self: Table[_] => - val commitId = column[String]("COMMIT_ID") - - def byCommit(owner: String, repository: String, commitId: String) = - byRepository(owner, repository) && (this.commitId === commitId) - - def byCommit(owner: Column[String], repository: Column[String], commitId: Column[String]) = - byRepository(userName, repositoryName) && (this.commitId === commitId) - } - -} diff --git a/src/main/scala/model/Collaborator.scala b/src/main/scala/model/Collaborator.scala deleted file mode 100644 index 88311e1..0000000 --- a/src/main/scala/model/Collaborator.scala +++ /dev/null @@ -1,21 +0,0 @@ -package model - -trait CollaboratorComponent extends TemplateComponent { self: Profile => - import profile.simple._ - - lazy val Collaborators = TableQuery[Collaborators] - - class Collaborators(tag: Tag) extends Table[Collaborator](tag, "COLLABORATOR") with BasicTemplate { - val collaboratorName = column[String]("COLLABORATOR_NAME") - def * = (userName, repositoryName, collaboratorName) <> (Collaborator.tupled, Collaborator.unapply) - - def byPrimaryKey(owner: String, repository: String, collaborator: String) = - byRepository(owner, repository) && (collaboratorName === collaborator.bind) - } -} - -case class Collaborator( - userName: String, - repositoryName: String, - collaboratorName: String -) diff --git a/src/main/scala/model/Comment.scala b/src/main/scala/model/Comment.scala deleted file mode 100644 index 9569871..0000000 --- a/src/main/scala/model/Comment.scala +++ /dev/null @@ -1,78 +0,0 @@ -package model - -trait Comment { - val commentedUserName: String - val registeredDate: java.util.Date -} - -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 === 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 -) extends Comment - -trait CommitCommentComponent extends TemplateComponent { self: Profile => - import profile.simple._ - import self._ - - lazy val CommitComments = new TableQuery(tag => new CommitComments(tag)){ - def autoInc = this returning this.map(_.commentId) - } - - class CommitComments(tag: Tag) extends Table[CommitComment](tag, "COMMIT_COMMENT") with CommitTemplate { - val commentId = column[Int]("COMMENT_ID", O AutoInc) - val commentedUserName = column[String]("COMMENTED_USER_NAME") - val content = column[String]("CONTENT") - val fileName = column[Option[String]]("FILE_NAME") - val oldLine = column[Option[Int]]("OLD_LINE_NUMBER") - val newLine = column[Option[Int]]("NEW_LINE_NUMBER") - 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, commitId, commentId, commentedUserName, content, fileName, oldLine, newLine, registeredDate, updatedDate, pullRequest) <> (CommitComment.tupled, CommitComment.unapply) - - def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind - } -} - -case class CommitComment( - userName: String, - repositoryName: String, - commitId: String, - commentId: Int = 0, - commentedUserName: String, - content: String, - fileName: Option[String], - oldLine: Option[Int], - newLine: Option[Int], - registeredDate: java.util.Date, - updatedDate: java.util.Date, - pullRequest: Boolean - ) extends Comment diff --git a/src/main/scala/model/CommitStatus.scala b/src/main/scala/model/CommitStatus.scala deleted file mode 100644 index 47a2343..0000000 --- a/src/main/scala/model/CommitStatus.scala +++ /dev/null @@ -1,71 +0,0 @@ -package model - -import scala.slick.lifted.MappedTo -import scala.slick.jdbc._ - -trait CommitStatusComponent extends TemplateComponent { self: Profile => - import profile.simple._ - import self._ - - implicit val commitStateColumnType = MappedColumnType.base[CommitState, String](b => b.name , i => CommitState(i)) - - lazy val CommitStatuses = TableQuery[CommitStatuses] - class CommitStatuses(tag: Tag) extends Table[CommitStatus](tag, "COMMIT_STATUS") with CommitTemplate { - val commitStatusId = column[Int]("COMMIT_STATUS_ID", O AutoInc) - val context = column[String]("CONTEXT") - val state = column[CommitState]("STATE") - val targetUrl = column[Option[String]]("TARGET_URL") - val description = column[Option[String]]("DESCRIPTION") - val creator = column[String]("CREATOR") - val registeredDate = column[java.util.Date]("REGISTERED_DATE") - val updatedDate = column[java.util.Date]("UPDATED_DATE") - def * = (commitStatusId, userName, repositoryName, commitId, context, state, targetUrl, description, creator, registeredDate, updatedDate) <> (CommitStatus.tupled, CommitStatus.unapply) - def byPrimaryKey(id: Int) = commitStatusId === id.bind - } -} - -case class CommitStatus( - commitStatusId: Int = 0, - userName: String, - repositoryName: String, - commitId: String, - context: String, - state: CommitState, - targetUrl: Option[String], - description: Option[String], - creator: String, - registeredDate: java.util.Date, - updatedDate: java.util.Date -) -sealed abstract class CommitState(val name: String) -object CommitState { - object ERROR extends CommitState("error") - object FAILURE extends CommitState("failure") - object PENDING extends CommitState("pending") - object SUCCESS extends CommitState("success") - - val values: Vector[CommitState] = Vector(PENDING, SUCCESS, ERROR, FAILURE) - private val map: Map[String, CommitState] = values.map(enum => enum.name -> enum).toMap - def apply(name: String): CommitState = map(name) - def valueOf(name: String): Option[CommitState] = map.get(name) - - /** - * failure if any of the contexts report as error or failure - * pending if there are no statuses or a context is pending - * success if the latest status for all contexts is success - */ - def combine(statuses: Set[CommitState]): CommitState = { - if(statuses.isEmpty){ - PENDING - }else if(statuses.contains(CommitState.ERROR) || statuses.contains(CommitState.FAILURE)){ - FAILURE - }else if(statuses.contains(CommitState.PENDING)){ - PENDING - }else{ - SUCCESS - } - } - - implicit val getResult: GetResult[CommitState] = GetResult(r => CommitState(r.<<)) - implicit val getResultOpt: GetResult[Option[CommitState]] = GetResult(r => r.<[String].map(CommitState(_))) -} diff --git a/src/main/scala/model/GroupMembers.scala b/src/main/scala/model/GroupMembers.scala deleted file mode 100644 index f0161d3..0000000 --- a/src/main/scala/model/GroupMembers.scala +++ /dev/null @@ -1,20 +0,0 @@ -package model - -trait GroupMemberComponent { self: Profile => - import profile.simple._ - - lazy val GroupMembers = TableQuery[GroupMembers] - - class GroupMembers(tag: Tag) extends Table[GroupMember](tag, "GROUP_MEMBER") { - val groupName = column[String]("GROUP_NAME", O PrimaryKey) - val userName = column[String]("USER_NAME", O PrimaryKey) - val isManager = column[Boolean]("MANAGER") - def * = (groupName, userName, isManager) <> (GroupMember.tupled, GroupMember.unapply) - } -} - -case class GroupMember( - groupName: String, - userName: String, - isManager: Boolean -) diff --git a/src/main/scala/model/Issue.scala b/src/main/scala/model/Issue.scala deleted file mode 100644 index 85c6014..0000000 --- a/src/main/scala/model/Issue.scala +++ /dev/null @@ -1,49 +0,0 @@ -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 -) diff --git a/src/main/scala/model/IssueLabels.scala b/src/main/scala/model/IssueLabels.scala deleted file mode 100644 index 5d42272..0000000 --- a/src/main/scala/model/IssueLabels.scala +++ /dev/null @@ -1,20 +0,0 @@ -package model - -trait IssueLabelComponent extends TemplateComponent { self: Profile => - import profile.simple._ - - lazy val IssueLabels = TableQuery[IssueLabels] - - class IssueLabels(tag: Tag) extends Table[IssueLabel](tag, "ISSUE_LABEL") with IssueTemplate with LabelTemplate { - def * = (userName, repositoryName, issueId, labelId) <> (IssueLabel.tupled, IssueLabel.unapply) - def byPrimaryKey(owner: String, repository: String, issueId: Int, labelId: Int) = - byIssue(owner, repository, issueId) && (this.labelId === labelId.bind) - } -} - -case class IssueLabel( - userName: String, - repositoryName: String, - issueId: Int, - labelId: Int -) diff --git a/src/main/scala/model/Labels.scala b/src/main/scala/model/Labels.scala deleted file mode 100644 index 47c6a2b..0000000 --- a/src/main/scala/model/Labels.scala +++ /dev/null @@ -1,37 +0,0 @@ -package model - -trait LabelComponent extends TemplateComponent { self: Profile => - import profile.simple._ - - lazy val Labels = TableQuery[Labels] - - class Labels(tag: Tag) extends Table[Label](tag, "LABEL") with LabelTemplate { - override val labelId = column[Int]("LABEL_ID", O AutoInc) - val labelName = column[String]("LABEL_NAME") - val color = column[String]("COLOR") - def * = (userName, repositoryName, labelId, labelName, color) <> (Label.tupled, Label.unapply) - - def byPrimaryKey(owner: String, repository: String, labelId: Int) = byLabel(owner, repository, labelId) - def byPrimaryKey(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) = byLabel(userName, repositoryName, labelId) - } -} - -case class Label( - userName: String, - repositoryName: String, - labelId: Int = 0, - labelName: String, - color: String){ - - val fontColor = { - val r = color.substring(0, 2) - val g = color.substring(2, 4) - val b = color.substring(4, 6) - - if(Integer.parseInt(r, 16) + Integer.parseInt(g, 16) + Integer.parseInt(b, 16) > 408){ - "000000" - } else { - "ffffff" - } - } -} diff --git a/src/main/scala/model/Milestone.scala b/src/main/scala/model/Milestone.scala deleted file mode 100644 index c392219..0000000 --- a/src/main/scala/model/Milestone.scala +++ /dev/null @@ -1,30 +0,0 @@ -package model - -trait MilestoneComponent extends TemplateComponent { self: Profile => - import profile.simple._ - import self._ - - lazy val Milestones = TableQuery[Milestones] - - class Milestones(tag: Tag) extends Table[Milestone](tag, "MILESTONE") with MilestoneTemplate { - override val milestoneId = column[Int]("MILESTONE_ID", O AutoInc) - val title = column[String]("TITLE") - val description = column[String]("DESCRIPTION") - val dueDate = column[java.util.Date]("DUE_DATE") - val closedDate = column[java.util.Date]("CLOSED_DATE") - def * = (userName, repositoryName, milestoneId, title, description.?, dueDate.?, closedDate.?) <> (Milestone.tupled, Milestone.unapply) - - def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId) - def byPrimaryKey(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) = byMilestone(userName, repositoryName, milestoneId) - } -} - -case class Milestone( - userName: String, - repositoryName: String, - milestoneId: Int = 0, - title: String, - description: Option[String], - dueDate: Option[java.util.Date], - closedDate: Option[java.util.Date] -) diff --git a/src/main/scala/model/Plugin.scala b/src/main/scala/model/Plugin.scala deleted file mode 100644 index bc85ca0..0000000 --- a/src/main/scala/model/Plugin.scala +++ /dev/null @@ -1,19 +0,0 @@ -package model - -trait PluginComponent extends TemplateComponent { self: Profile => - import profile.simple._ - import self._ - - lazy val Plugins = TableQuery[Plugins] - - class Plugins(tag: Tag) extends Table[Plugin](tag, "PLUGIN"){ - val pluginId = column[String]("PLUGIN_ID", O PrimaryKey) - val version = column[String]("VERSION") - def * = (pluginId, version) <> (Plugin.tupled, Plugin.unapply) - } -} - -case class Plugin( - pluginId: String, - version: String -) diff --git a/src/main/scala/model/Profile.scala b/src/main/scala/model/Profile.scala deleted file mode 100644 index b88bb05..0000000 --- a/src/main/scala/model/Profile.scala +++ /dev/null @@ -1,45 +0,0 @@ -package model - -trait Profile { - val profile: slick.driver.JdbcProfile - import profile.simple._ - - // java.util.Date Mapped Column Types - implicit val dateColumnType = MappedColumnType.base[java.util.Date, java.sql.Timestamp]( - d => new java.sql.Timestamp(d.getTime), - t => new java.util.Date(t.getTime) - ) - - implicit class RichColumn(c1: Column[Boolean]){ - def &&(c2: => Column[Boolean], guard: => Boolean): Column[Boolean] = if(guard) c1 && c2 else c1 - } - -} - -object Profile extends { - val profile = slick.driver.H2Driver - -} with AccessTokenComponent - with AccountComponent - with ActivityComponent - with CollaboratorComponent - with CommitCommentComponent - with CommitStatusComponent - with GroupMemberComponent - with IssueComponent - with IssueCommentComponent - with IssueLabelComponent - with LabelComponent - with MilestoneComponent - with PullRequestComponent - with RepositoryComponent - with SshKeyComponent - with WebHookComponent - with PluginComponent with Profile { - - /** - * Returns system date. - */ - def currentDate = new java.util.Date() - -} diff --git a/src/main/scala/model/PullRequest.scala b/src/main/scala/model/PullRequest.scala deleted file mode 100644 index 3ba87ea..0000000 --- a/src/main/scala/model/PullRequest.scala +++ /dev/null @@ -1,32 +0,0 @@ -package model - -trait PullRequestComponent extends TemplateComponent { self: Profile => - import profile.simple._ - - lazy val PullRequests = TableQuery[PullRequests] - - class PullRequests(tag: Tag) extends Table[PullRequest](tag, "PULL_REQUEST") with IssueTemplate { - val branch = column[String]("BRANCH") - val requestUserName = column[String]("REQUEST_USER_NAME") - val requestRepositoryName = column[String]("REQUEST_REPOSITORY_NAME") - val requestBranch = column[String]("REQUEST_BRANCH") - val commitIdFrom = column[String]("COMMIT_ID_FROM") - val commitIdTo = column[String]("COMMIT_ID_TO") - def * = (userName, repositoryName, issueId, branch, requestUserName, requestRepositoryName, requestBranch, commitIdFrom, commitIdTo) <> (PullRequest.tupled, PullRequest.unapply) - - def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) = byIssue(userName, repositoryName, issueId) - def byPrimaryKey(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) = byIssue(userName, repositoryName, issueId) - } -} - -case class PullRequest( - userName: String, - repositoryName: String, - issueId: Int, - branch: String, - requestUserName: String, - requestRepositoryName: String, - requestBranch: String, - commitIdFrom: String, - commitIdTo: String -) diff --git a/src/main/scala/model/Repository.scala b/src/main/scala/model/Repository.scala deleted file mode 100644 index 5a888fc..0000000 --- a/src/main/scala/model/Repository.scala +++ /dev/null @@ -1,39 +0,0 @@ -package model - -trait RepositoryComponent extends TemplateComponent { self: Profile => - import profile.simple._ - import self._ - - lazy val Repositories = TableQuery[Repositories] - - class Repositories(tag: Tag) extends Table[Repository](tag, "REPOSITORY") with BasicTemplate { - val isPrivate = column[Boolean]("PRIVATE") - val description = column[String]("DESCRIPTION") - val defaultBranch = column[String]("DEFAULT_BRANCH") - val registeredDate = column[java.util.Date]("REGISTERED_DATE") - val updatedDate = column[java.util.Date]("UPDATED_DATE") - val lastActivityDate = column[java.util.Date]("LAST_ACTIVITY_DATE") - val originUserName = column[String]("ORIGIN_USER_NAME") - val originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME") - val parentUserName = column[String]("PARENT_USER_NAME") - val parentRepositoryName = column[String]("PARENT_REPOSITORY_NAME") - def * = (userName, repositoryName, isPrivate, description.?, defaultBranch, registeredDate, updatedDate, lastActivityDate, originUserName.?, originRepositoryName.?, parentUserName.?, parentRepositoryName.?) <> (Repository.tupled, Repository.unapply) - - def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository) - } -} - -case class Repository( - userName: String, - repositoryName: String, - isPrivate: Boolean, - description: Option[String], - defaultBranch: String, - registeredDate: java.util.Date, - updatedDate: java.util.Date, - lastActivityDate: java.util.Date, - originUserName: Option[String], - originRepositoryName: Option[String], - parentUserName: Option[String], - parentRepositoryName: Option[String] -) diff --git a/src/main/scala/model/SshKey.scala b/src/main/scala/model/SshKey.scala deleted file mode 100644 index dcf3463..0000000 --- a/src/main/scala/model/SshKey.scala +++ /dev/null @@ -1,24 +0,0 @@ -package model - -trait SshKeyComponent { self: Profile => - import profile.simple._ - - lazy val SshKeys = TableQuery[SshKeys] - - class SshKeys(tag: Tag) extends Table[SshKey](tag, "SSH_KEY") { - val userName = column[String]("USER_NAME") - val sshKeyId = column[Int]("SSH_KEY_ID", O AutoInc) - val title = column[String]("TITLE") - val publicKey = column[String]("PUBLIC_KEY") - def * = (userName, sshKeyId, title, publicKey) <> (SshKey.tupled, SshKey.unapply) - - def byPrimaryKey(userName: String, sshKeyId: Int) = (this.userName === userName.bind) && (this.sshKeyId === sshKeyId.bind) - } -} - -case class SshKey( - userName: String, - sshKeyId: Int = 0, - title: String, - publicKey: String -) diff --git a/src/main/scala/model/WebHook.scala b/src/main/scala/model/WebHook.scala deleted file mode 100644 index 4c13c87..0000000 --- a/src/main/scala/model/WebHook.scala +++ /dev/null @@ -1,20 +0,0 @@ -package model - -trait WebHookComponent extends TemplateComponent { self: Profile => - import profile.simple._ - - lazy val WebHooks = TableQuery[WebHooks] - - class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate { - val url = column[String]("URL") - def * = (userName, repositoryName, url) <> (WebHook.tupled, WebHook.unapply) - - def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind) - } -} - -case class WebHook( - userName: String, - repositoryName: String, - url: String -) diff --git a/src/main/scala/model/package.scala b/src/main/scala/model/package.scala deleted file mode 100644 index c65e72e..0000000 --- a/src/main/scala/model/package.scala +++ /dev/null @@ -1,3 +0,0 @@ -package object model { - type Session = slick.jdbc.JdbcBackend#Session -} diff --git a/src/main/scala/plugin/Plugin.scala b/src/main/scala/plugin/Plugin.scala deleted file mode 100644 index c176874..0000000 --- a/src/main/scala/plugin/Plugin.scala +++ /dev/null @@ -1,30 +0,0 @@ -package plugin - -import javax.servlet.ServletContext - -import util.Version - -/** - * Trait for define plugin interface. - * To provide plugin, put Plugin class which mixed in this trait into the package root. - */ -trait Plugin { - - val pluginId: String - val pluginName: String - val description: String - val versions: Seq[Version] - - /** - * This method is invoked in initialization of plugin system. - * Register plugin functionality to PluginRegistry. - */ - def initialize(registry: PluginRegistry): Unit - - /** - * This method is invoked in shutdown of plugin system. - * If the plugin has any resources, release them in this method. - */ - def shutdown(registry: PluginRegistry): Unit - -} diff --git a/src/main/scala/plugin/PluginRegistory.scala b/src/main/scala/plugin/PluginRegistory.scala deleted file mode 100644 index bb7c2cb..0000000 --- a/src/main/scala/plugin/PluginRegistory.scala +++ /dev/null @@ -1,145 +0,0 @@ -package plugin - -import java.io.{FilenameFilter, File} -import java.net.URLClassLoader -import javax.servlet.ServletContext -import javax.servlet.http.{HttpServletRequest, HttpServletResponse} - -import org.slf4j.LoggerFactory -import service.RepositoryService.RepositoryInfo -import util.Directory._ -import util.JDBCUtil._ -import util.{Version, Versions} - -import scala.collection.mutable.ListBuffer -import app.{ControllerBase, Context} - -class PluginRegistry { - - private val plugins = new ListBuffer[PluginInfo] - private val javaScripts = new ListBuffer[(String, String)] - private val controllers = new ListBuffer[(ControllerBase, String)] - - def addPlugin(pluginInfo: PluginInfo): Unit = { - plugins += pluginInfo - } - - def getPlugins(): List[PluginInfo] = plugins.toList - - def addController(controller: ControllerBase, path: String): Unit = { - controllers += ((controller, path)) - } - - def getControllers(): List[(ControllerBase, String)] = controllers.toList - - def addJavaScript(path: String, script: String): Unit = { - javaScripts += Tuple2(path, script) - } - - //def getJavaScripts(): List[(String, String)] = javaScripts.toList - - def getJavaScript(currentPath: String): Option[String] = { - javaScripts.find(x => currentPath.matches(x._1)).map(_._2) - } - - private case class GlobalAction( - method: String, - path: String, - function: (HttpServletRequest, HttpServletResponse, Context) => Any - ) - - private case class RepositoryAction( - method: String, - path: String, - function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any - ) - -} - -/** - * Provides entry point to PluginRegistry. - */ -object PluginRegistry { - - private val logger = LoggerFactory.getLogger(classOf[PluginRegistry]) - - private val instance = new PluginRegistry() - - /** - * Returns the PluginRegistry singleton instance. - */ - def apply(): PluginRegistry = instance - - /** - * Initializes all installed plugins. - */ - def initialize(context: ServletContext, conn: java.sql.Connection): Unit = { - val pluginDir = new File(PluginHome) - if(pluginDir.exists && pluginDir.isDirectory){ - pluginDir.listFiles(new FilenameFilter { - override def accept(dir: File, name: String): Boolean = name.endsWith(".jar") - }).foreach { pluginJar => - val classLoader = new URLClassLoader(Array(pluginJar.toURI.toURL), Thread.currentThread.getContextClassLoader) - try { - val plugin = classLoader.loadClass("Plugin").newInstance().asInstanceOf[Plugin] - - // Migration - val headVersion = plugin.versions.head - val currentVersion = conn.find("SELECT * FROM PLUGIN WHERE PLUGIN_ID = ?", plugin.pluginId)(_.getString("VERSION")) match { - case Some(x) => { - val dim = x.split("\\.") - Version(dim(0).toInt, dim(1).toInt) - } - case None => Version(0, 0) - } - - Versions.update(conn, headVersion, currentVersion, plugin.versions, new URLClassLoader(Array(pluginJar.toURI.toURL))){ conn => - currentVersion.versionString match { - case "0.0" => - conn.update("INSERT INTO PLUGIN (PLUGIN_ID, VERSION) VALUES (?, ?)", plugin.pluginId, headVersion.versionString) - case _ => - conn.update("UPDATE PLUGIN SET VERSION = ? WHERE PLUGIN_ID = ?", headVersion.versionString, plugin.pluginId) - } - } - - // Initialize - plugin.initialize(instance) - instance.addPlugin(PluginInfo( - pluginId = plugin.pluginId, - pluginName = plugin.pluginName, - version = plugin.versions.head.versionString, - description = plugin.description, - pluginClass = plugin - )) - - } catch { - case e: Exception => { - logger.error(s"Error during plugin initialization", e) - } - } - } - } - } - - def shutdown(context: ServletContext): Unit = { - instance.getPlugins().foreach { pluginInfo => - try { - pluginInfo.pluginClass.shutdown(instance) - } catch { - case e: Exception => { - logger.error(s"Error during plugin shutdown", e) - } - } - } - } - - -} - -case class PluginInfo( - pluginId: String, - pluginName: String, - version: String, - description: String, - pluginClass: Plugin -) \ No newline at end of file diff --git a/src/main/scala/plugin/Results.scala b/src/main/scala/plugin/Results.scala deleted file mode 100644 index 18fdb7f..0000000 --- a/src/main/scala/plugin/Results.scala +++ /dev/null @@ -1,11 +0,0 @@ -package plugin - -import play.twirl.api.Html - -/** - * Defines result case classes returned by plugin controller. - */ -object Results { - case class Redirect(path: String) - case class Fragment(html: Html) -} diff --git a/src/main/scala/plugin/Sessions.scala b/src/main/scala/plugin/Sessions.scala deleted file mode 100644 index 7398c9a..0000000 --- a/src/main/scala/plugin/Sessions.scala +++ /dev/null @@ -1,11 +0,0 @@ -package plugin - -import slick.jdbc.JdbcBackend.Session - -/** - * Provides Slick Session to Plug-ins. - */ -object Sessions { - val sessions = new ThreadLocal[Session] - implicit def session: Session = sessions.get() -} diff --git a/src/main/scala/service/AccesTokenService.scala b/src/main/scala/service/AccesTokenService.scala deleted file mode 100644 index 4de5816..0000000 --- a/src/main/scala/service/AccesTokenService.scala +++ /dev/null @@ -1,52 +0,0 @@ -package service - -import model.Profile._ -import profile.simple._ -import model.{Account, AccessToken} -import util.StringUtil -import scala.util.Random - -trait AccessTokenService { - - def makeAccessTokenString: String = { - val bytes = new Array[Byte](20) - Random.nextBytes(bytes) - bytes.map("%02x".format(_)).mkString - } - - def tokenToHash(token: String): String = StringUtil.sha1(token) - - /** - * @retuen (TokenId, Token) - */ - def generateAccessToken(userName: String, note: String)(implicit s: Session): (Int, String) = { - var token: String = null - var hash: String = null - do{ - token = makeAccessTokenString - hash = tokenToHash(token) - }while(AccessTokens.filter(_.tokenHash === hash.bind).exists.run) - val newToken = AccessToken( - userName = userName, - note = note, - tokenHash = hash) - val tokenId = (AccessTokens returning AccessTokens.map(_.accessTokenId)) += newToken - (tokenId, token) - } - - def getAccountByAccessToken(token: String)(implicit s: Session): Option[Account] = - Accounts - .innerJoin(AccessTokens) - .filter{ case (ac, t) => (ac.userName === t.userName) && (t.tokenHash === tokenToHash(token).bind) && (ac.removed === false.bind) } - .map{ case (ac, t) => ac } - .firstOption - - def getAccessTokens(userName: String)(implicit s: Session): List[AccessToken] = - AccessTokens.filter(_.userName === userName.bind).sortBy(_.accessTokenId.desc).list - - def deleteAccessToken(userName: String, accessTokenId: Int)(implicit s: Session): Unit = - AccessTokens filter (t => t.userName === userName.bind && t.accessTokenId === accessTokenId) delete - -} - -object AccessTokenService extends AccessTokenService diff --git a/src/main/scala/service/AccountService.scala b/src/main/scala/service/AccountService.scala deleted file mode 100644 index 88d95fa..0000000 --- a/src/main/scala/service/AccountService.scala +++ /dev/null @@ -1,188 +0,0 @@ -package service - -import model.Profile._ -import profile.simple._ -import model.{Account, GroupMember} -// TODO [Slick 2.0]NOT import directly? -import model.Profile.dateColumnType -import service.SystemSettingsService.SystemSettings -import util.StringUtil._ -import util.LDAPUtil -import org.slf4j.LoggerFactory - -trait AccountService { - - private val logger = LoggerFactory.getLogger(classOf[AccountService]) - - def authenticate(settings: SystemSettings, userName: String, password: String)(implicit s: Session): Option[Account] = - if(settings.ldapAuthentication){ - ldapAuthentication(settings, userName, password) - } else { - defaultAuthentication(userName, password) - } - - /** - * Authenticate by internal database. - */ - private def defaultAuthentication(userName: String, password: String)(implicit s: Session) = { - getAccountByUserName(userName).collect { - case account if(!account.isGroupAccount && account.password == sha1(password)) => Some(account) - } getOrElse None - } - - /** - * Authenticate by LDAP. - */ - private def ldapAuthentication(settings: SystemSettings, userName: String, password: String) - (implicit s: Session): Option[Account] = { - LDAPUtil.authenticate(settings.ldap.get, userName, password) match { - case Right(ldapUserInfo) => { - // Create or update account by LDAP information - getAccountByUserName(ldapUserInfo.userName, true) match { - case Some(x) if(!x.isRemoved) => { - if(settings.ldap.get.mailAttribute.getOrElse("").isEmpty) { - updateAccount(x.copy(fullName = ldapUserInfo.fullName)) - } else { - updateAccount(x.copy(mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName)) - } - getAccountByUserName(ldapUserInfo.userName) - } - case Some(x) if(x.isRemoved) => { - logger.info("LDAP Authentication Failed: Account is already registered but disabled.") - defaultAuthentication(userName, password) - } - case None => getAccountByMailAddress(ldapUserInfo.mailAddress, true) match { - case Some(x) if(!x.isRemoved) => { - updateAccount(x.copy(fullName = ldapUserInfo.fullName)) - getAccountByUserName(ldapUserInfo.userName) - } - case Some(x) if(x.isRemoved) => { - logger.info("LDAP Authentication Failed: Account is already registered but disabled.") - defaultAuthentication(userName, password) - } - case None => { - createAccount(ldapUserInfo.userName, "", ldapUserInfo.fullName, ldapUserInfo.mailAddress, false, None) - getAccountByUserName(ldapUserInfo.userName) - } - } - } - } - case Left(errorMessage) => { - logger.info(s"LDAP Authentication Failed: ${errorMessage}") - defaultAuthentication(userName, password) - } - } - } - - def getAccountByUserName(userName: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] = - Accounts filter(t => (t.userName === userName.bind) && (t.removed === false.bind, !includeRemoved)) firstOption - - def getAccountsByUserNames(userNames: Set[String], knowns:Set[Account], includeRemoved: Boolean = false)(implicit s: Session): Map[String, Account] = { - val map = knowns.map(a => a.userName -> a).toMap - val needs = userNames -- map.keySet - if(needs.isEmpty){ - map - }else{ - map ++ Accounts.filter(t => (t.userName inSetBind needs) && (t.removed === false.bind, !includeRemoved)).list.map(a => a.userName -> a).toMap - } - } - - def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] = - Accounts filter(t => (t.mailAddress.toLowerCase === mailAddress.toLowerCase.bind) && (t.removed === false.bind, !includeRemoved)) firstOption - - def getAllUsers(includeRemoved: Boolean = true)(implicit s: Session): List[Account] = - if(includeRemoved){ - Accounts sortBy(_.userName) list - } else { - Accounts filter (_.removed === false.bind) sortBy(_.userName) list - } - - def createAccount(userName: String, password: String, fullName: String, mailAddress: String, isAdmin: Boolean, url: Option[String]) - (implicit s: Session): Unit = - Accounts insert Account( - userName = userName, - password = password, - fullName = fullName, - mailAddress = mailAddress, - isAdmin = isAdmin, - url = url, - registeredDate = currentDate, - updatedDate = currentDate, - lastLoginDate = None, - image = None, - isGroupAccount = false, - isRemoved = false) - - def updateAccount(account: Account)(implicit s: Session): Unit = - Accounts - .filter { a => a.userName === account.userName.bind } - .map { a => (a.password, a.fullName, a.mailAddress, a.isAdmin, a.url.?, a.registeredDate, a.updatedDate, a.lastLoginDate.?, a.removed) } - .update ( - account.password, - account.fullName, - account.mailAddress, - account.isAdmin, - account.url, - account.registeredDate, - currentDate, - account.lastLoginDate, - account.isRemoved) - - def updateAvatarImage(userName: String, image: Option[String])(implicit s: Session): Unit = - Accounts.filter(_.userName === userName.bind).map(_.image.?).update(image) - - def updateLastLoginDate(userName: String)(implicit s: Session): Unit = - Accounts.filter(_.userName === userName.bind).map(_.lastLoginDate).update(currentDate) - - def createGroup(groupName: String, url: Option[String])(implicit s: Session): Unit = - Accounts insert Account( - userName = groupName, - password = "", - fullName = groupName, - mailAddress = groupName + "@devnull", - isAdmin = false, - url = url, - registeredDate = currentDate, - updatedDate = currentDate, - lastLoginDate = None, - image = None, - isGroupAccount = true, - isRemoved = false) - - def updateGroup(groupName: String, url: Option[String], removed: Boolean)(implicit s: Session): Unit = - Accounts.filter(_.userName === groupName.bind).map(t => t.url.? -> t.removed).update(url, removed) - - def updateGroupMembers(groupName: String, members: List[(String, Boolean)])(implicit s: Session): Unit = { - GroupMembers.filter(_.groupName === groupName.bind).delete - members.foreach { case (userName, isManager) => - GroupMembers insert GroupMember (groupName, userName, isManager) - } - } - - def getGroupMembers(groupName: String)(implicit s: Session): List[GroupMember] = - GroupMembers - .filter(_.groupName === groupName.bind) - .sortBy(_.userName) - .list - - def getGroupsByUserName(userName: String)(implicit s: Session): List[String] = - GroupMembers - .filter(_.userName === userName.bind) - .sortBy(_.groupName) - .map(_.groupName) - .list - - def removeUserRelatedData(userName: String)(implicit s: Session): Unit = { - GroupMembers.filter(_.userName === userName.bind).delete - Collaborators.filter(_.collaboratorName === userName.bind).delete - Repositories.filter(_.userName === userName.bind).delete - } - - def getGroupNames(userName: String)(implicit s: Session): List[String] = { - List(userName) ++ - Collaborators.filter(_.collaboratorName === userName.bind).sortBy(_.userName).map(_.userName).list - } - -} - -object AccountService extends AccountService diff --git a/src/main/scala/service/ActivityService.scala b/src/main/scala/service/ActivityService.scala deleted file mode 100644 index b1e8202..0000000 --- a/src/main/scala/service/ActivityService.scala +++ /dev/null @@ -1,188 +0,0 @@ -package service - -import model.Profile._ -import profile.simple._ -import model.Activity - -trait ActivityService { - - def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit s: Session): List[Activity] = - Activities - .innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) - .filter { case (t1, t2) => - if(isPublic){ - (t1.activityUserName === activityUserName.bind) && (t2.isPrivate === false.bind) - } else { - (t1.activityUserName === activityUserName.bind) - } - } - .sortBy { case (t1, t2) => t1.activityId desc } - .map { case (t1, t2) => t1 } - .take(30) - .list - - def getRecentActivities()(implicit s: Session): List[Activity] = - Activities - .innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) - .filter { case (t1, t2) => t2.isPrivate === false.bind } - .sortBy { case (t1, t2) => t1.activityId desc } - .map { case (t1, t2) => t1 } - .take(30) - .list - - def getRecentActivitiesByOwners(owners : Set[String])(implicit s: Session): List[Activity] = - Activities - .innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) - .filter { case (t1, t2) => (t2.isPrivate === false.bind) || (t2.userName inSetBind owners) } - .sortBy { case (t1, t2) => t1.activityId desc } - .map { case (t1, t2) => t1 } - .take(30) - .list - - def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "create_repository", - s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]", - None, - currentDate) - - def recordCreateIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "open_issue", - s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]", - Some(title), - currentDate) - - def recordCloseIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "close_issue", - s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]", - Some(title), - currentDate) - - def recordClosePullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "close_issue", - s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]", - Some(title), - currentDate) - - def recordReopenIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "reopen_issue", - s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]", - Some(title), - currentDate) - - def recordCommentIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "comment_issue", - s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]", - Some(cut(comment, 200)), - currentDate) - - def recordCommentPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "comment_issue", - s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]", - Some(cut(comment, 200)), - currentDate) - - def recordCommentCommitActivity(userName: String, repositoryName: String, activityUserName: String, commitId: String, comment: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "comment_commit", - s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]", - Some(cut(comment, 200)), - currentDate - ) - - def recordCreateWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "create_wiki", - s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki", - Some(pageName), - currentDate) - - def recordEditWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String, commitId: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "edit_wiki", - s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki", - Some(pageName + ":" + commitId), - currentDate) - - def recordPushActivity(userName: String, repositoryName: String, activityUserName: String, - branchName: String, commits: List[util.JGitUtil.CommitInfo])(implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "push", - s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", - Some(commits.map { commit => commit.id + ":" + commit.shortMessage }.mkString("\n")), - currentDate) - - def recordCreateTagActivity(userName: String, repositoryName: String, activityUserName: String, - tagName: String, commits: List[util.JGitUtil.CommitInfo])(implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "create_tag", - s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]", - None, - currentDate) - - def recordDeleteTagActivity(userName: String, repositoryName: String, activityUserName: String, - tagName: String, commits: List[util.JGitUtil.CommitInfo])(implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "delete_tag", - s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]", - None, - currentDate) - - def recordCreateBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "create_branch", - s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", - None, - currentDate) - - def recordDeleteBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "delete_branch", - s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]", - None, - currentDate) - - def recordForkActivity(userName: String, repositoryName: String, activityUserName: String, forkedUserName: String)(implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "fork", - s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${forkedUserName}/${repositoryName}]", - None, - currentDate) - - def recordPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "open_pullreq", - s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]", - Some(title), - currentDate) - - def recordMergeActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, message: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, - "merge_pullreq", - s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]", - Some(message), - currentDate) - - private def cut(value: String, length: Int): String = - if(value.length > length) value.substring(0, length) + "..." else value -} diff --git a/src/main/scala/service/CommitStatusService.scala b/src/main/scala/service/CommitStatusService.scala deleted file mode 100644 index 8860f77..0000000 --- a/src/main/scala/service/CommitStatusService.scala +++ /dev/null @@ -1,50 +0,0 @@ -package service - -import model.Profile._ -import profile.simple._ -import model.{CommitState, CommitStatus, Account} -import util.Implicits._ -import util.StringUtil._ -import service.RepositoryService.RepositoryInfo - -trait CommitStatusService { - /** insert or update */ - def createCommitStatus(userName: String, repositoryName: String, sha:String, context:String, state:CommitState, targetUrl:Option[String], description:Option[String], now:java.util.Date, creator:Account)(implicit s: Session): Int = - CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) && t.context===context.bind ) - .map(_.commitStatusId).firstOption match { - case Some(id:Int) => { - CommitStatuses.filter(_.byPrimaryKey(id)).map{ - t => (t.state , t.targetUrl , t.updatedDate , t.creator, t.description) - }.update( (state, targetUrl, now, creator.userName, description) ) - id - } - case None => (CommitStatuses returning CommitStatuses.map(_.commitStatusId)) += CommitStatus( - userName = userName, - repositoryName = repositoryName, - commitId = sha, - context = context, - state = state, - targetUrl = targetUrl, - description = description, - creator = creator.userName, - registeredDate = now, - updatedDate = now) - } - - def getCommitStatus(userName: String, repositoryName: String, id: Int)(implicit s: Session) :Option[CommitStatus] = - CommitStatuses.filter(t => t.byPrimaryKey(id) && t.byRepository(userName, repositoryName)).firstOption - - def getCommitStatus(userName: String, repositoryName: String, sha: String, context: String)(implicit s: Session) :Option[CommitStatus] = - CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) && t.context===context.bind ).firstOption - - def getCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) :List[CommitStatus] = - byCommitStatues(userName, repositoryName, sha).list - - def getCommitStatuesWithCreator(userName: String, repositoryName: String, sha: String)(implicit s: Session) :List[(CommitStatus, Account)] = - byCommitStatues(userName, repositoryName, sha).innerJoin(Accounts) - .filter{ case (t,a) => t.creator === a.userName }.list - - protected def byCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) = - CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) ).sortBy(_.updatedDate desc) - -} \ No newline at end of file diff --git a/src/main/scala/service/CommitsService.scala b/src/main/scala/service/CommitsService.scala deleted file mode 100644 index 6f70e3c..0000000 --- a/src/main/scala/service/CommitsService.scala +++ /dev/null @@ -1,52 +0,0 @@ -package service - -import scala.slick.jdbc.{StaticQuery => Q} -import Q.interpolation - -import model.Profile._ -import profile.simple._ -import model.CommitComment -import util.Implicits._ -import util.StringUtil._ - - -trait CommitsService { - - def getCommitComments(owner: String, repository: String, commitId: String, pullRequest: Boolean)(implicit s: Session) = - CommitComments filter { - t => t.byCommit(owner, repository, commitId) && (t.pullRequest === pullRequest || pullRequest) - } list - - def getCommitComment(owner: String, repository: String, commentId: String)(implicit s: Session) = - if (commentId forall (_.isDigit)) - CommitComments filter { t => - t.byPrimaryKey(commentId.toInt) && t.byRepository(owner, repository) - } firstOption - else - None - - def createCommitComment(owner: String, repository: String, commitId: String, loginUser: String, - content: String, fileName: Option[String], oldLine: Option[Int], newLine: Option[Int], pullRequest: Boolean)(implicit s: Session): Int = - CommitComments.autoInc insert CommitComment( - userName = owner, - repositoryName = repository, - commitId = commitId, - commentedUserName = loginUser, - content = content, - fileName = fileName, - oldLine = oldLine, - newLine = newLine, - registeredDate = currentDate, - updatedDate = currentDate, - pullRequest = pullRequest) - - def updateCommitComment(commentId: Int, content: String)(implicit s: Session) = - CommitComments - .filter (_.byPrimaryKey(commentId)) - .map { t => - t.content -> t.updatedDate - }.update (content, currentDate) - - def deleteCommitComment(commentId: Int)(implicit s: Session) = - CommitComments filter (_.byPrimaryKey(commentId)) delete -} diff --git a/src/main/scala/service/IssuesService.scala b/src/main/scala/service/IssuesService.scala deleted file mode 100644 index 0edc884..0000000 --- a/src/main/scala/service/IssuesService.scala +++ /dev/null @@ -1,540 +0,0 @@ -package service - -import scala.slick.jdbc.{StaticQuery => Q} -import Q.interpolation - -import model.Profile._ -import profile.simple._ -import model.{Issue, IssueComment, IssueLabel, Label} -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 getCommentsForApi(owner: String, repository: String, issueId: Int)(implicit s: Session) = - IssueComments.filter(_.byIssue(owner, repository, issueId)) - .filter(_.action inSetBind Set("comment" , "close_comment", "reopen_comment")) - .innerJoin(Accounts).on( (t1, t2) => t1.userName === t2.userName ) - .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 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, onlyPullRequest: Boolean, - repos: (String, String)*)(implicit s: Session): Int = - Query(searchIssueQuery(repos, condition, 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 - * @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), 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 - } - - def getCommitStatues(issueList:Seq[(String, String, Int)])(implicit s: Session) :Map[(String, String, Int), CommitStatusInfo] ={ - if(issueList.isEmpty){ - Map.empty - }else{ - import scala.slick.jdbc._ - val issueIdQuery = issueList.map(i => "(PR.USER_NAME=? AND PR.REPOSITORY_NAME=? AND PR.ISSUE_ID=?)").mkString(" OR ") - implicit val qset = SetParameter[Seq[(String, String, Int)]] { - case (seq, pp) => - for (a <- seq) { - pp.setString(a._1) - pp.setString(a._2) - pp.setInt(a._3) - } - } - import model.Profile.commitStateColumnType - val query = Q.query[Seq[(String, String, Int)], (String, String, Int, Int, Int, Option[String], Option[model.CommitState], Option[String], Option[String])](s""" - SELECT SUMM.USER_NAME, SUMM.REPOSITORY_NAME, SUMM.ISSUE_ID, CS_ALL, CS_SUCCESS - , CSD.CONTEXT, CSD.STATE, CSD.TARGET_URL, CSD.DESCRIPTION - FROM (SELECT - PR.USER_NAME - , PR.REPOSITORY_NAME - , PR.ISSUE_ID - , COUNT(CS.STATE) AS CS_ALL - , SUM(CS.STATE='success') AS CS_SUCCESS - , PR.COMMIT_ID_TO AS COMMIT_ID - FROM PULL_REQUEST PR - JOIN COMMIT_STATUS CS - ON PR.USER_NAME=CS.USER_NAME - AND PR.REPOSITORY_NAME=CS.REPOSITORY_NAME - AND PR.COMMIT_ID_TO=CS.COMMIT_ID - WHERE $issueIdQuery - GROUP BY PR.USER_NAME, PR.REPOSITORY_NAME, PR.ISSUE_ID) as SUMM - LEFT OUTER JOIN COMMIT_STATUS CSD - ON SUMM.CS_ALL = 1 AND SUMM.COMMIT_ID = CSD.COMMIT_ID"""); - query(issueList).list.map{ - case(userName, repositoryName, issueId, count, successCount, context, state, targetUrl, description) => - (userName, repositoryName, issueId) -> CommitStatusInfo(count, successCount, context, state, targetUrl, description) - }.toMap - } - } - - /** - * Returns the search result against issues. - * - * @param condition the search condition - * @param pullRequest if true then returns only pull requests, false then returns only issues. - * @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, pullRequest: Boolean, offset: Int, limit: Int, repos: (String, String)*) - (implicit s: Session): List[IssueInfo] = { - // get issues and comment count and labels - val result = searchIssueQueryBase(condition, pullRequest, offset, limit, repos) - .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 (Milestones) .on { case ((((t1, t2), t3), t4), t5) => t1.byMilestone(t5.userName, t5.repositoryName, t5.milestoneId) } - .map { case ((((t1, t2), t3), t4), t5) => - (t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?, t5.title.?) - } - .list - .splitWith { (c1, c2) => - c1._1.userName == c2._1.userName && - c1._1.repositoryName == c2._1.repositoryName && - c1._1.issueId == c2._1.issueId - } - val status = getCommitStatues(result.map(_.head._1).map(is => (is.userName, is.repositoryName, is.issueId))) - - result.map { issues => issues.head match { - case (issue, commentCount, _, _, _, milestone) => - IssueInfo(issue, - issues.flatMap { t => t._3.map ( - Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get) - )} toList, - milestone, - commentCount, - status.get(issue.userName, issue.repositoryName, issue.issueId)) - }} toList - } - - /** for api - * @return (issue, commentCount, pullRequest, headRepository, headOwner) - */ - def searchPullRequestByApi(condition: IssueSearchCondition, offset: Int, limit: Int, repos: (String, String)*) - (implicit s: Session): List[(Issue, model.Account, Int, model.PullRequest, model.Repository, model.Account)] = { - // get issues and comment count and labels - searchIssueQueryBase(condition, true, offset, limit, repos) - .innerJoin(PullRequests).on { case ((t1, t2), t3) => t3.byPrimaryKey(t1.userName, t1.repositoryName, t1.issueId) } - .innerJoin(Repositories).on { case (((t1, t2), t3), t4) => t4.byRepository(t1.userName, t1.repositoryName) } - .innerJoin(Accounts).on { case ((((t1, t2), t3), t4), t5) => t5.userName === t1.userName } - .innerJoin(Accounts).on { case (((((t1, t2), t3), t4), t5), t6) => t6.userName === t4.userName } - .map { case (((((t1, t2), t3), t4), t5), t6) => - (t1, t5, t2.commentCount, t3, t4, t6) - } - .list - } - - private def searchIssueQueryBase(condition: IssueSearchCondition, pullRequest: Boolean, offset: Int, limit: Int, repos: Seq[(String, String)]) - (implicit s: Session) = - searchIssueQuery(repos, condition, pullRequest) - .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) - - - /** - * Assembles query for conditional issue searching. - */ - private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition, pullRequest: Boolean)(implicit s: Session) = - Issues filter { t1 => - repos - .map { case (owner, repository) => t1.byRepository(owner, repository) } - .foldLeft[Column[Boolean]](false) ( _ || _ ) && - (t1.closed === (condition.state == "closed").bind) && - //(t1.milestoneId === condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) && - (t1.milestoneId.? isEmpty, condition.milestone == Some(None)) && - (t1.assignedUserName === condition.assigned.get.bind, condition.assigned.isDefined) && - (t1.openedUserName === condition.author.get.bind, condition.author.isDefined) && - (t1.pullRequest === pullRequest.bind) && - // Milestone filter - (Milestones filter { t2 => - (t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.milestoneId)) && - (t2.title === condition.milestone.get.get.bind) - } exists, condition.milestone.flatten.isDefined) && - // Label filter - (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) && - // Visibility filter - (Repositories filter { t2 => - (t2.byRepository(t1.userName, t1.repositoryName)) && - (t2.isPrivate === (condition.visibility == Some("private")).bind) - } exists, condition.visibility.nonEmpty) && - // Organization (group) filter - (t1.userName inSetBind condition.groups, condition.groups.nonEmpty) && - // Mentioned filter - ((t1.openedUserName === condition.mentioned.get.bind) || t1.assignedUserName === condition.mentioned.get.bind || - (IssueComments filter { t2 => - (t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.commentedUserName === condition.mentioned.get.bind) - } exists), condition.mentioned.isDefined) - } - - 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 - .filter(_.byRepository(owner, repository)) - .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 - .filter(_.byRepository(owner, repository)) - .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, - milestone: Option[Option[String]] = None, - author: Option[String] = None, - assigned: Option[String] = None, - mentioned: Option[String] = None, - state: String = "open", - sort: String = "created", - direction: String = "desc", - visibility: Option[String] = None, - groups: Set[String] = Set.empty){ - - def isEmpty: Boolean = { - labels.isEmpty && milestone.isEmpty && author.isEmpty && assigned.isEmpty && - state == "open" && sort == "created" && direction == "desc" && visibility.isEmpty - } - - def nonEmpty: Boolean = !isEmpty - - def toFilterString: String = ( - List( - Some(s"is:${state}"), - author.map(author => s"author:${author}"), - assigned.map(assignee => s"assignee:${assignee}"), - mentioned.map(mentioned => s"mentions:${mentioned}") - ).flatten ++ - labels.map(label => s"label:${label}") ++ - List( - milestone.map { _ match { - case Some(x) => s"milestone:${x}" - case None => "no:milestone" - }}, - (sort, direction) match { - case ("created" , "desc") => None - case ("created" , "asc" ) => Some("sort:created-asc") - case ("comments", "desc") => Some("sort:comments-desc") - case ("comments", "asc" ) => Some("sort:comments-asc") - case ("updated" , "desc") => Some("sort:updated-desc") - case ("updated" , "asc" ) => Some("sort:updated-asc") - }, - visibility.map(visibility => s"visibility:${visibility}") - ).flatten ++ - groups.map(group => s"group:${group}") - ).mkString(" ") - - def toURL: String = - "?" + List( - if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))), - milestone.map { _ match { - case Some(x) => "milestone=" + urlEncode(x) - case None => "milestone=none" - }}, - author .map(x => "author=" + urlEncode(x)), - assigned .map(x => "assigned=" + urlEncode(x)), - mentioned.map(x => "mentioned=" + urlEncode(x)), - Some("state=" + urlEncode(state)), - Some("sort=" + urlEncode(sort)), - Some("direction=" + urlEncode(direction)), - visibility.map(x => "visibility=" + urlEncode(x)), - if(groups.isEmpty) None else Some("groups=" + urlEncode(groups.mkString(","))) - ).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) - } - - /** - * Restores IssueSearchCondition instance from filter query. - */ - def apply(filter: String, milestones: Map[String, Int]): IssueSearchCondition = { - val conditions = filter.split("[ \t]+").map { x => - val dim = x.split(":") - dim(0) -> dim(1) - }.groupBy(_._1).map { case (key, values) => - key -> values.map(_._2).toSeq - } - - val (sort, direction) = conditions.get("sort").flatMap(_.headOption).getOrElse("created-desc") match { - case "created-asc" => ("created" , "asc" ) - case "comments-desc" => ("comments", "desc") - case "comments-asc" => ("comments", "asc" ) - case "updated-desc" => ("comments", "desc") - case "updated-asc" => ("comments", "asc" ) - case _ => ("created" , "desc") - } - - IssueSearchCondition( - conditions.get("label").map(_.toSet).getOrElse(Set.empty), - conditions.get("milestone").flatMap(_.headOption) match { - case None => None - case Some("none") => Some(None) - case Some(x) => Some(Some(x)) //milestones.get(x).map(x => Some(x)) - }, - conditions.get("author").flatMap(_.headOption), - conditions.get("assignee").flatMap(_.headOption), - conditions.get("mentions").flatMap(_.headOption), - conditions.get("is").getOrElse(Seq.empty).find(x => x == "open" || x == "closed").getOrElse("open"), - sort, - direction, - conditions.get("visibility").flatMap(_.headOption), - conditions.get("group").map(_.toSet).getOrElse(Set.empty) - ) - } - - /** - * Restores IssueSearchCondition instance from request parameters. - */ - def apply(request: HttpServletRequest): IssueSearchCondition = - IssueSearchCondition( - param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty), - param(request, "milestone").map { - case "none" => None - case x => Some(x) - }, - param(request, "author"), - param(request, "assigned"), - param(request, "mentioned"), - 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"), - param(request, "visibility"), - param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty) - ) - - def page(request: HttpServletRequest) = try { - val i = param(request, "page").getOrElse("1").toInt - if(i <= 0) 1 else i - } catch { - case e: NumberFormatException => 1 - } - } - - case class CommitStatusInfo(count: Int, successCount: Int, context: Option[String], state: Option[model.CommitState], targetUrl: Option[String], description: Option[String]) - - case class IssueInfo(issue: Issue, labels: List[Label], milestone: Option[String], commentCount: Int, status:Option[CommitStatusInfo]) - -} diff --git a/src/main/scala/service/LabelsService.scala b/src/main/scala/service/LabelsService.scala deleted file mode 100644 index de1dcb8..0000000 --- a/src/main/scala/service/LabelsService.scala +++ /dev/null @@ -1,34 +0,0 @@ -package service - -import model.Profile._ -import profile.simple._ -import model.Label - -trait LabelsService { - - def getLabels(owner: String, repository: String)(implicit s: Session): List[Label] = - Labels.filter(_.byRepository(owner, repository)).sortBy(_.labelName asc).list - - def getLabel(owner: String, repository: String, labelId: Int)(implicit s: Session): Option[Label] = - Labels.filter(_.byPrimaryKey(owner, repository, labelId)).firstOption - - def createLabel(owner: String, repository: String, labelName: String, color: String)(implicit s: Session): Int = - Labels returning Labels.map(_.labelId) += Label( - userName = owner, - repositoryName = repository, - labelName = labelName, - color = color - ) - - def updateLabel(owner: String, repository: String, labelId: Int, labelName: String, color: String) - (implicit s: Session): Unit = - Labels.filter(_.byPrimaryKey(owner, repository, labelId)) - .map(t => t.labelName -> t.color) - .update(labelName, color) - - def deleteLabel(owner: String, repository: String, labelId: Int)(implicit s: Session): Unit = { - IssueLabels.filter(_.byLabel(owner, repository, labelId)).delete - Labels.filter(_.byPrimaryKey(owner, repository, labelId)).delete - } - -} diff --git a/src/main/scala/service/MergeService.scala b/src/main/scala/service/MergeService.scala deleted file mode 100644 index b2168ac..0000000 --- a/src/main/scala/service/MergeService.scala +++ /dev/null @@ -1,168 +0,0 @@ -package service -import util.LockUtil -import util.Directory._ -import util.Implicits._ -import util.ControlUtil._ -import org.eclipse.jgit.merge.MergeStrategy -import org.eclipse.jgit.api.Git -import org.eclipse.jgit.transport.RefSpec -import org.eclipse.jgit.errors.NoMergeBaseException -import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent} -import model.Account -import org.eclipse.jgit.revwalk.RevWalk -trait MergeService { - import MergeService._ - /** - * Checks whether conflict will be caused in merging within pull request. - * Returns true if conflict will be caused. - */ - def checkConflict(userName: String, repositoryName: String, branch: String, issueId: Int): Boolean = { - using(Git.open(getRepositoryDir(userName, repositoryName))) { git => - MergeCacheInfo(git, branch, issueId).checkConflict() - } - } - /** - * Checks whether conflict will be caused in merging within pull request. - * only cache check. - * Returns Some(true) if conflict will be caused. - * Returns None if cache has not created yet. - */ - def checkConflictCache(userName: String, repositoryName: String, branch: String, issueId: Int): Option[Boolean] = { - using(Git.open(getRepositoryDir(userName, repositoryName))) { git => - MergeCacheInfo(git, branch, issueId).checkConflictCache() - } - } - /** merge pull request */ - def mergePullRequest(git:Git, branch: String, issueId: Int, message:String, committer:PersonIdent): Unit = { - MergeCacheInfo(git, branch, issueId).merge(message, committer) - } - /** fetch remote branch to my repository refs/pull/{issueId}/head */ - def fetchAsPullRequest(userName: String, repositoryName: String, requestUserName: String, requestRepositoryName: String, requestBranch:String, issueId:Int){ - using(Git.open(getRepositoryDir(userName, repositoryName))){ git => - git.fetch - .setRemote(getRepositoryDir(requestUserName, requestRepositoryName).toURI.toString) - .setRefSpecs(new RefSpec(s"refs/heads/${requestBranch}:refs/pull/${issueId}/head")) - .call - } - } - /** - * Checks whether conflict will be caused in merging. Returns true if conflict will be caused. - */ - def checkConflict(userName: String, repositoryName: String, branch: String, - requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = { - using(Git.open(getRepositoryDir(requestUserName, requestRepositoryName))) { git => - val remoteRefName = s"refs/heads/${branch}" - val tmpRefName = s"refs/merge-check/${userName}/${branch}" - val refSpec = new RefSpec(s"${remoteRefName}:${tmpRefName}").setForceUpdate(true) - try { - // fetch objects from origin repository branch - git.fetch - .setRemote(getRepositoryDir(userName, repositoryName).toURI.toString) - .setRefSpecs(refSpec) - .call - // merge conflict check - val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true) - val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${requestBranch}") - val mergeTip = git.getRepository.resolve(tmpRefName) - try { - !merger.merge(mergeBaseTip, mergeTip) - } catch { - case e: NoMergeBaseException => true - } - } finally { - val refUpdate = git.getRepository.updateRef(refSpec.getDestination) - refUpdate.setForceUpdate(true) - refUpdate.delete() - } - } - } -} -object MergeService{ - case class MergeCacheInfo(git:Git, branch:String, issueId:Int){ - val repository = git.getRepository - val mergedBranchName = s"refs/pull/${issueId}/merge" - val conflictedBranchName = s"refs/pull/${issueId}/conflict" - lazy val mergeBaseTip = repository.resolve(s"refs/heads/${branch}") - lazy val mergeTip = repository.resolve(s"refs/pull/${issueId}/head") - def checkConflictCache(): Option[Boolean] = { - Option(repository.resolve(mergedBranchName)).flatMap{ merged => - if(parseCommit( merged ).getParents().toSet == Set( mergeBaseTip, mergeTip )){ - // merged branch exists - Some(false) - }else{ - None - } - }.orElse(Option(repository.resolve(conflictedBranchName)).flatMap{ conflicted => - if(parseCommit( conflicted ).getParents().toSet == Set( mergeBaseTip, mergeTip )){ - // conflict branch exists - Some(true) - }else{ - None - } - }) - } - def checkConflict():Boolean ={ - checkConflictCache.getOrElse(checkConflictForce) - } - def checkConflictForce():Boolean ={ - val merger = MergeStrategy.RECURSIVE.newMerger(repository, true) - val conflicted = try { - !merger.merge(mergeBaseTip, mergeTip) - } catch { - case e: NoMergeBaseException => true - } - val mergeTipCommit = using(new RevWalk( repository ))(_.parseCommit( mergeTip )) - val committer = mergeTipCommit.getCommitterIdent; - def updateBranch(treeId:ObjectId, message:String, branchName:String){ - // creates merge commit - val mergeCommitId = createMergeCommit(treeId, committer, message) - // update refs - val refUpdate = repository.updateRef(branchName) - refUpdate.setNewObjectId(mergeCommitId) - refUpdate.setForceUpdate(true) - refUpdate.setRefLogIdent(committer) - refUpdate.update() - } - if(!conflicted){ - updateBranch(merger.getResultTreeId, s"Merge ${mergeTip.name} into ${mergeBaseTip.name}", mergedBranchName) - git.branchDelete().setForce(true).setBranchNames(conflictedBranchName).call() - }else{ - updateBranch(mergeTipCommit.getTree().getId(), s"can't merge ${mergeTip.name} into ${mergeBaseTip.name}", conflictedBranchName) - git.branchDelete().setForce(true).setBranchNames(mergedBranchName).call() - } - conflicted - } - // update branch from cache - def merge(message:String, committer:PersonIdent) = { - if(checkConflict()){ - throw new RuntimeException("This pull request can't merge automatically.") - } - val mergeResultCommit = parseCommit( Option(repository.resolve(mergedBranchName)).getOrElse(throw new RuntimeException(s"not found branch ${mergedBranchName}")) ) - // creates merge commit - val mergeCommitId = createMergeCommit(mergeResultCommit.getTree().getId(), committer, message) - // update refs - val refUpdate = repository.updateRef(s"refs/heads/${branch}") - refUpdate.setNewObjectId(mergeCommitId) - refUpdate.setForceUpdate(false) - refUpdate.setRefLogIdent(committer) - refUpdate.setRefLogMessage("merged", true) - refUpdate.update() - } - // return treeId - private def createMergeCommit(treeId:ObjectId, committer:PersonIdent, message:String) = { - val mergeCommit = new CommitBuilder() - mergeCommit.setTreeId(treeId) - mergeCommit.setParentIds(Array[ObjectId](mergeBaseTip, mergeTip): _*) - mergeCommit.setAuthor(committer) - mergeCommit.setCommitter(committer) - mergeCommit.setMessage(message) - // insertObject and got mergeCommit Object Id - val inserter = repository.newObjectInserter - val mergeCommitId = inserter.insert(mergeCommit) - inserter.flush() - inserter.release() - mergeCommitId - } - private def parseCommit(id:ObjectId) = using(new RevWalk( repository ))(_.parseCommit(id)) - } -} \ No newline at end of file diff --git a/src/main/scala/service/MilestonesService.scala b/src/main/scala/service/MilestonesService.scala deleted file mode 100644 index 476e0c4..0000000 --- a/src/main/scala/service/MilestonesService.scala +++ /dev/null @@ -1,57 +0,0 @@ -package service - -import model.Profile._ -import profile.simple._ -import model.Milestone -// TODO [Slick 2.0]NOT import directly? -import model.Profile.dateColumnType - -trait MilestonesService { - - def createMilestone(owner: String, repository: String, title: String, description: Option[String], - dueDate: Option[java.util.Date])(implicit s: Session): Unit = - Milestones insert Milestone( - userName = owner, - repositoryName = repository, - title = title, - description = description, - dueDate = dueDate, - closedDate = None - ) - - def updateMilestone(milestone: Milestone)(implicit s: Session): Unit = - Milestones - .filter (t => t.byPrimaryKey(milestone.userName, milestone.repositoryName, milestone.milestoneId)) - .map (t => (t.title, t.description.?, t.dueDate.?, t.closedDate.?)) - .update (milestone.title, milestone.description, milestone.dueDate, milestone.closedDate) - - def openMilestone(milestone: Milestone)(implicit s: Session): Unit = - updateMilestone(milestone.copy(closedDate = None)) - - def closeMilestone(milestone: Milestone)(implicit s: Session): Unit = - updateMilestone(milestone.copy(closedDate = Some(currentDate))) - - def deleteMilestone(owner: String, repository: String, milestoneId: Int)(implicit s: Session): Unit = { - Issues.filter(_.byMilestone(owner, repository, milestoneId)).map(_.milestoneId.?).update(None) - Milestones.filter(_.byPrimaryKey(owner, repository, milestoneId)).delete - } - - def getMilestone(owner: String, repository: String, milestoneId: Int)(implicit s: Session): Option[Milestone] = - Milestones.filter(_.byPrimaryKey(owner, repository, milestoneId)).firstOption - - def getMilestonesWithIssueCount(owner: String, repository: String)(implicit s: Session): List[(Milestone, Int, Int)] = { - val counts = Issues - .filter { t => (t.byRepository(owner, repository)) && (t.milestoneId.? isDefined) } - .groupBy { t => t.milestoneId -> t.closed } - .map { case (t1, t2) => t1._1 -> t1._2 -> t2.length } - .toMap - - getMilestones(owner, repository).map { milestone => - (milestone, counts.getOrElse((milestone.milestoneId, false), 0), counts.getOrElse((milestone.milestoneId, true), 0)) - } - } - - def getMilestones(owner: String, repository: String)(implicit s: Session): List[Milestone] = - Milestones.filter(_.byRepository(owner, repository)).sortBy(_.milestoneId asc).list - -} diff --git a/src/main/scala/service/PluginService.scala b/src/main/scala/service/PluginService.scala deleted file mode 100644 index d1bb9d8..0000000 --- a/src/main/scala/service/PluginService.scala +++ /dev/null @@ -1,24 +0,0 @@ -package service - -import model.Profile._ -import profile.simple._ -import model.Plugin - -trait PluginService { - - def getPlugins()(implicit s: Session): List[Plugin] = - Plugins.sortBy(_.pluginId).list - - def registerPlugin(plugin: Plugin)(implicit s: Session): Unit = - Plugins.insert(plugin) - - def updatePlugin(plugin: Plugin)(implicit s: Session): Unit = - Plugins.filter(_.pluginId === plugin.pluginId.bind).map(_.version).update(plugin.version) - - def deletePlugin(pluginId: String)(implicit s: Session): Unit = - Plugins.filter(_.pluginId === pluginId.bind).delete - - def getPlugin(pluginId: String)(implicit s: Session): Option[Plugin] = - Plugins.filter(_.pluginId === pluginId.bind).firstOption - -} diff --git a/src/main/scala/service/PullRequestService.scala b/src/main/scala/service/PullRequestService.scala deleted file mode 100644 index 19c20d9..0000000 --- a/src/main/scala/service/PullRequestService.scala +++ /dev/null @@ -1,125 +0,0 @@ -package service - -import model.Profile._ -import profile.simple._ -import model.{PullRequest, Issue, WebHook, Account} -import util.JGitUtil - -trait PullRequestService { self: IssuesService => - import PullRequestService._ - - def getPullRequest(owner: String, repository: String, issueId: Int) - (implicit s: Session): Option[(Issue, PullRequest)] = - getIssue(owner, repository, issueId.toString).flatMap{ issue => - PullRequests.filter(_.byPrimaryKey(owner, repository, issueId)).firstOption.map{ - pullreq => (issue, pullreq) - } - } - - def updateCommitId(owner: String, repository: String, issueId: Int, commitIdTo: String, commitIdFrom: String) - (implicit s: Session): Unit = - PullRequests.filter(_.byPrimaryKey(owner, repository, issueId)) - .map(pr => pr.commitIdTo -> pr.commitIdFrom) - .update((commitIdTo, commitIdFrom)) - - def getPullRequestCountGroupByUser(closed: Boolean, owner: Option[String], repository: Option[String]) - (implicit s: Session): List[PullRequestCount] = - PullRequests - .innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) } - .filter { case (t1, t2) => - (t2.closed === closed.bind) && - (t1.userName === owner.get.bind, owner.isDefined) && - (t1.repositoryName === repository.get.bind, repository.isDefined) - } - .groupBy { case (t1, t2) => t2.openedUserName } - .map { case (userName, t) => userName -> t.length } - .sortBy(_._2 desc) - .list - .map { x => PullRequestCount(x._1, x._2) } - -// def getAllPullRequestCountGroupByUser(closed: Boolean, userName: String)(implicit s: Session): List[PullRequestCount] = -// PullRequests -// .innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) } -// .innerJoin(Repositories).on { case ((t1, t2), t3) => t2.byRepository(t3.userName, t3.repositoryName) } -// .filter { case ((t1, t2), t3) => -// (t2.closed === closed.bind) && -// ( -// (t3.isPrivate === false.bind) || -// (t3.userName === userName.bind) || -// (Collaborators.filter { t4 => t4.byRepository(t3.userName, t3.repositoryName) && (t4.collaboratorName === userName.bind)} exists) -// ) -// } -// .groupBy { case ((t1, t2), t3) => t2.openedUserName } -// .map { case (userName, t) => userName -> t.length } -// .sortBy(_._2 desc) -// .list -// .map { x => PullRequestCount(x._1, x._2) } - - def createPullRequest(originUserName: String, originRepositoryName: String, issueId: Int, - originBranch: String, requestUserName: String, requestRepositoryName: String, requestBranch: String, - commitIdFrom: String, commitIdTo: String)(implicit s: Session): Unit = - PullRequests insert PullRequest( - originUserName, - originRepositoryName, - issueId, - originBranch, - requestUserName, - requestRepositoryName, - requestBranch, - commitIdFrom, - commitIdTo) - - def getPullRequestsByRequest(userName: String, repositoryName: String, branch: String, closed: Boolean) - (implicit s: Session): List[PullRequest] = - PullRequests - .innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) } - .filter { case (t1, t2) => - (t1.requestUserName === userName.bind) && - (t1.requestRepositoryName === repositoryName.bind) && - (t1.requestBranch === branch.bind) && - (t2.closed === closed.bind) - } - .map { case (t1, t2) => t1 } - .list - - /** - * Fetch pull request contents into refs/pull/${issueId}/head and update pull request table. - */ - def updatePullRequests(owner: String, repository: String, branch: String)(implicit s: Session): Unit = - getPullRequestsByRequest(owner, repository, branch, false).foreach { pullreq => - if(Repositories.filter(_.byRepository(pullreq.userName, pullreq.repositoryName)).exists.run){ - val (commitIdTo, commitIdFrom) = JGitUtil.updatePullRequest( - pullreq.userName, pullreq.repositoryName, pullreq.branch, pullreq.issueId, - pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch) - updateCommitId(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdTo, commitIdFrom) - } - } - - def getPullRequestByRequestCommit(userName: String, repositoryName: String, toBranch:String, fromBranch: String, commitId: String) - (implicit s: Session): Option[(PullRequest, Issue)] = { - if(toBranch == fromBranch){ - None - } else { - PullRequests - .innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) } - .filter { case (t1, t2) => - (t1.userName === userName.bind) && - (t1.repositoryName === repositoryName.bind) && - (t1.branch === toBranch.bind) && - (t1.requestUserName === userName.bind) && - (t1.requestRepositoryName === repositoryName.bind) && - (t1.requestBranch === fromBranch.bind) && - (t1.commitIdTo === commitId.bind) - } - .firstOption - } - } -} - -object PullRequestService { - - val PullRequestLimit = 25 - - case class PullRequestCount(userName: String, count: Int) - -} diff --git a/src/main/scala/service/RepositorySearchService.scala b/src/main/scala/service/RepositorySearchService.scala deleted file mode 100644 index f727af1..0000000 --- a/src/main/scala/service/RepositorySearchService.scala +++ /dev/null @@ -1,128 +0,0 @@ -package service - -import util.{FileUtil, StringUtil, JGitUtil} -import util.Directory._ -import util.ControlUtil._ -import org.eclipse.jgit.revwalk.RevWalk -import org.eclipse.jgit.treewalk.TreeWalk -import org.eclipse.jgit.lib.FileMode -import org.eclipse.jgit.api.Git -import model.Profile._ -import profile.simple._ - -trait RepositorySearchService { self: IssuesService => - import RepositorySearchService._ - - def countIssues(owner: String, repository: String, query: String)(implicit session: Session): Int = - searchIssuesByKeyword(owner, repository, query).length - - def searchIssues(owner: String, repository: String, query: String)(implicit session: Session): List[IssueSearchResult] = - searchIssuesByKeyword(owner, repository, query).map { case (issue, commentCount, content) => - IssueSearchResult( - issue.issueId, - issue.isPullRequest, - issue.title, - issue.openedUserName, - issue.registeredDate, - commentCount, - getHighlightText(content, query)._1) - } - - def countFiles(owner: String, repository: String, query: String): Int = - using(Git.open(getRepositoryDir(owner, repository))){ git => - if(JGitUtil.isEmpty(git)) 0 else searchRepositoryFiles(git, query).length - } - - def searchFiles(owner: String, repository: String, query: String): List[FileSearchResult] = - using(Git.open(getRepositoryDir(owner, repository))){ git => - if(JGitUtil.isEmpty(git)){ - Nil - } else { - val files = searchRepositoryFiles(git, query) - val commits = JGitUtil.getLatestCommitFromPaths(git, files.map(_._1), "HEAD") - files.map { case (path, text) => - val (highlightText, lineNumber) = getHighlightText(text, query) - FileSearchResult( - path, - commits(path).getCommitterIdent.getWhen, - highlightText, - lineNumber) - } - } - } - - private def searchRepositoryFiles(git: Git, query: String): List[(String, String)] = { - val revWalk = new RevWalk(git.getRepository) - val objectId = git.getRepository.resolve("HEAD") - val revCommit = revWalk.parseCommit(objectId) - val treeWalk = new TreeWalk(git.getRepository) - treeWalk.setRecursive(true) - treeWalk.addTree(revCommit.getTree) - - val keywords = StringUtil.splitWords(query.toLowerCase) - val list = new scala.collection.mutable.ListBuffer[(String, String)] - - while (treeWalk.next()) { - val mode = treeWalk.getFileMode(0) - if(mode == FileMode.REGULAR_FILE || mode == FileMode.EXECUTABLE_FILE){ - JGitUtil.getContentFromId(git, treeWalk.getObjectId(0), false).foreach { bytes => - if(FileUtil.isText(bytes)){ - val text = StringUtil.convertFromByteArray(bytes) - val lowerText = text.toLowerCase - val indices = keywords.map(lowerText.indexOf _) - if(!indices.exists(_ < 0)){ - list.append((treeWalk.getPathString, text)) - } - } - } - } - } - treeWalk.release - revWalk.release - - list.toList - } - -} - -object RepositorySearchService { - - val CodeLimit = 10 - val IssueLimit = 10 - - def getHighlightText(content: String, query: String): (String, Int) = { - val keywords = StringUtil.splitWords(query.toLowerCase) - val lowerText = content.toLowerCase - val indices = keywords.map(lowerText.indexOf _) - - if(!indices.exists(_ < 0)){ - val lineNumber = content.substring(0, indices.min).split("\n").size - 1 - val highlightText = StringUtil.escapeHtml(content.split("\n").drop(lineNumber).take(5).mkString("\n")) - .replaceAll("(?i)(" + keywords.map("\\Q" + _ + "\\E").mkString("|") + ")", - "$1") - (highlightText, lineNumber + 1) - } else { - (content.split("\n").take(5).mkString("\n"), 1) - } - } - - case class SearchResult( - files : List[(String, String)], - issues: List[(model.Issue, Int, String)]) - - case class IssueSearchResult( - issueId: Int, - isPullRequest: Boolean, - title: String, - openedUserName: String, - registeredDate: java.util.Date, - commentCount: Int, - highlightText: String) - - case class FileSearchResult( - path: String, - lastModified: java.util.Date, - highlightText: String, - highlightLineNumber: Int) - -} diff --git a/src/main/scala/service/RepositoryService.scala b/src/main/scala/service/RepositoryService.scala deleted file mode 100644 index 34ece5b..0000000 --- a/src/main/scala/service/RepositoryService.scala +++ /dev/null @@ -1,400 +0,0 @@ -package service - -import model.Profile._ -import profile.simple._ -import model.{Repository, Account, Collaborator, Label} -import util.JGitUtil - -trait RepositoryService { self: AccountService => - import RepositoryService._ - - /** - * Creates a new repository. - * - * @param repositoryName the repository name - * @param userName the user name of the repository owner - * @param description the repository description - * @param isPrivate the repository type (private is true, otherwise false) - * @param originRepositoryName specify for the forked repository. (default is None) - * @param originUserName specify for the forked repository. (default is None) - */ - def createRepository(repositoryName: String, userName: String, description: Option[String], isPrivate: Boolean, - originRepositoryName: Option[String] = None, originUserName: Option[String] = None, - parentRepositoryName: Option[String] = None, parentUserName: Option[String] = None) - (implicit s: Session): Unit = { - Repositories insert - Repository( - userName = userName, - repositoryName = repositoryName, - isPrivate = isPrivate, - description = description, - defaultBranch = "master", - registeredDate = currentDate, - updatedDate = currentDate, - lastActivityDate = currentDate, - originUserName = originUserName, - originRepositoryName = originRepositoryName, - parentUserName = parentUserName, - parentRepositoryName = parentRepositoryName) - - IssueId insert (userName, repositoryName, 0) - } - - def renameRepository(oldUserName: String, oldRepositoryName: String, newUserName: String, newRepositoryName: String) - (implicit s: Session): Unit = { - getAccountByUserName(newUserName).foreach { account => - (Repositories filter { t => t.byRepository(oldUserName, oldRepositoryName) } firstOption).map { repository => - Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName) - - val webHooks = WebHooks .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val milestones = Milestones .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val issueId = IssueId .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val issues = Issues .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val pullRequests = PullRequests .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val labels = Labels .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val issueComments = IssueComments .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val issueLabels = IssueLabels .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val commitComments = CommitComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val commitStatuses = CommitStatuses.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val collaborators = Collaborators .filter(_.byRepository(oldUserName, oldRepositoryName)).list - - Repositories.filter { t => - (t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind) - }.map { t => t.originUserName -> t.originRepositoryName }.update(newUserName, newRepositoryName) - - Repositories.filter { t => - (t.parentUserName === oldUserName.bind) && (t.parentRepositoryName === oldRepositoryName.bind) - }.map { t => t.originUserName -> t.originRepositoryName }.update(newUserName, newRepositoryName) - - PullRequests.filter { t => - t.requestRepositoryName === oldRepositoryName.bind - }.map { t => t.requestUserName -> t.requestRepositoryName }.update(newUserName, newRepositoryName) - - // Updates activity fk before deleting repository because activity is sorted by activityId - // and it can't be changed by deleting-and-inserting record. - Activities.filter(_.byRepository(oldUserName, oldRepositoryName)).list.foreach { activity => - Activities.filter(_.activityId === activity.activityId.bind) - .map(x => (x.userName, x.repositoryName)).update(newUserName, newRepositoryName) - } - - deleteRepository(oldUserName, oldRepositoryName) - - WebHooks .insertAll(webHooks .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - Milestones.insertAll(milestones .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - IssueId .insertAll(issueId .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*) - - val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list - Issues.insertAll(issues.map { x => x.copy( - userName = newUserName, - repositoryName = newRepositoryName, - milestoneId = x.milestoneId.map { id => - newMilestones.find(_.title == milestones.find(_.milestoneId == id).get.title).get.milestoneId - } - )} :_*) - - PullRequests .insertAll(pullRequests .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - Labels .insertAll(labels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - CommitComments.insertAll(commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - CommitStatuses.insertAll(commitStatuses.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - - // Convert labelId - val oldLabelMap = labels.map(x => (x.labelId, x.labelName)).toMap - val newLabelMap = Labels.filter(_.byRepository(newUserName, newRepositoryName)).map(x => (x.labelName, x.labelId)).list.toMap - IssueLabels.insertAll(issueLabels.map(x => x.copy( - labelId = newLabelMap(oldLabelMap(x.labelId)), - userName = newUserName, - repositoryName = newRepositoryName - )) :_*) - - if(account.isGroupAccount){ - Collaborators.insertAll(getGroupMembers(newUserName).map(m => Collaborator(newUserName, newRepositoryName, m.userName)) :_*) - } else { - Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - } - - // Update activity messages - Activities.filter { t => - (t.message like s"%:${oldUserName}/${oldRepositoryName}]%") || - (t.message like s"%:${oldUserName}/${oldRepositoryName}#%") || - (t.message like s"%:${oldUserName}/${oldRepositoryName}@%") - }.map { t => t.activityId -> t.message }.list.foreach { case (activityId, message) => - Activities.filter(_.activityId === activityId.bind).map(_.message).update( - message - .replace(s"[repo:${oldUserName}/${oldRepositoryName}]" ,s"[repo:${newUserName}/${newRepositoryName}]") - .replace(s"[branch:${oldUserName}/${oldRepositoryName}#" ,s"[branch:${newUserName}/${newRepositoryName}#") - .replace(s"[tag:${oldUserName}/${oldRepositoryName}#" ,s"[tag:${newUserName}/${newRepositoryName}#") - .replace(s"[pullreq:${oldUserName}/${oldRepositoryName}#",s"[pullreq:${newUserName}/${newRepositoryName}#") - .replace(s"[issue:${oldUserName}/${oldRepositoryName}#" ,s"[issue:${newUserName}/${newRepositoryName}#") - .replace(s"[commit:${oldUserName}/${oldRepositoryName}@" ,s"[commit:${newUserName}/${newRepositoryName}@") - ) - } - } - } - } - - def deleteRepository(userName: String, repositoryName: String)(implicit s: Session): Unit = { - Activities .filter(_.byRepository(userName, repositoryName)).delete - Collaborators .filter(_.byRepository(userName, repositoryName)).delete - CommitComments.filter(_.byRepository(userName, repositoryName)).delete - IssueLabels .filter(_.byRepository(userName, repositoryName)).delete - Labels .filter(_.byRepository(userName, repositoryName)).delete - IssueComments .filter(_.byRepository(userName, repositoryName)).delete - PullRequests .filter(_.byRepository(userName, repositoryName)).delete - Issues .filter(_.byRepository(userName, repositoryName)).delete - IssueId .filter(_.byRepository(userName, repositoryName)).delete - Milestones .filter(_.byRepository(userName, repositoryName)).delete - WebHooks .filter(_.byRepository(userName, repositoryName)).delete - Repositories .filter(_.byRepository(userName, repositoryName)).delete - - // Update ORIGIN_USER_NAME and ORIGIN_REPOSITORY_NAME - Repositories - .filter { x => (x.originUserName === userName.bind) && (x.originRepositoryName === repositoryName.bind) } - .map { x => (x.userName, x.repositoryName) } - .list - .foreach { case (userName, repositoryName) => - Repositories - .filter(_.byRepository(userName, repositoryName)) - .map(x => (x.originUserName?, x.originRepositoryName?)) - .update(None, None) - } - - // Update PARENT_USER_NAME and PARENT_REPOSITORY_NAME - Repositories - .filter { x => (x.parentUserName === userName.bind) && (x.parentRepositoryName === repositoryName.bind) } - .map { x => (x.userName, x.repositoryName) } - .list - .foreach { case (userName, repositoryName) => - Repositories - .filter(_.byRepository(userName, repositoryName)) - .map(x => (x.parentUserName?, x.parentRepositoryName?)) - .update(None, None) - } - } - - /** - * Returns the repository names of the specified user. - * - * @param userName the user name of repository owner - * @return the list of repository names - */ - def getRepositoryNamesOfUser(userName: String)(implicit s: Session): List[String] = - Repositories filter(_.userName === userName.bind) map (_.repositoryName) list - - /** - * Returns the specified repository information. - * - * @param userName the user name of the repository owner - * @param repositoryName the repository name - * @param baseUrl the base url of this application - * @return the repository information - */ - def getRepository(userName: String, repositoryName: String, baseUrl: String)(implicit s: Session): Option[RepositoryInfo] = { - (Repositories filter { t => t.byRepository(userName, repositoryName) } firstOption) map { repository => - // for getting issue count and pull request count - val issues = Issues.filter { t => - t.byRepository(repository.userName, repository.repositoryName) && (t.closed === false.bind) - }.map(_.pullRequest).list - - new RepositoryInfo( - JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl), - repository, - issues.count(_ == false), - issues.count(_ == true), - getForkedCount( - repository.originUserName.getOrElse(repository.userName), - repository.originRepositoryName.getOrElse(repository.repositoryName) - ), - getRepositoryManagers(repository.userName)) - } - } - - /** - * Returns the repositories without private repository that user does not have access right. - * Include public repository, private own repository and private but collaborator repository. - * - * @param userName the user name of collaborator - * @return the repository infomation list - */ - def getAllRepositories(userName: String)(implicit s: Session): List[(String, String)] = { - Repositories.filter { t1 => - (t1.isPrivate === false.bind) || - (t1.userName === userName.bind) || - (Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName === userName.bind)} exists) - }.sortBy(_.lastActivityDate desc).map{ t => - (t.userName, t.repositoryName) - }.list - } - - def getUserRepositories(userName: String, baseUrl: String, withoutPhysicalInfo: Boolean = false) - (implicit s: Session): List[RepositoryInfo] = { - Repositories.filter { t1 => - (t1.userName === userName.bind) || - (Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName === userName.bind)} exists) - }.sortBy(_.lastActivityDate desc).list.map{ repository => - new RepositoryInfo( - if(withoutPhysicalInfo){ - new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName, baseUrl) - } else { - JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl) - }, - repository, - getForkedCount( - repository.originUserName.getOrElse(repository.userName), - repository.originRepositoryName.getOrElse(repository.repositoryName) - ), - getRepositoryManagers(repository.userName)) - } - } - - /** - * Returns the list of visible repositories for the specified user. - * If repositoryUserName is given then filters results by repository owner. - * - * @param loginAccount the logged in account - * @param baseUrl the base url of this application - * @param repositoryUserName the repository owner (if None then returns all repositories which are visible for logged in user) - * @param withoutPhysicalInfo if true then the result does not include physical repository information such as commit count, - * branches and tags - * @return the repository information which is sorted in descending order of lastActivityDate. - */ - def getVisibleRepositories(loginAccount: Option[Account], baseUrl: String, repositoryUserName: Option[String] = None, - withoutPhysicalInfo: Boolean = false) - (implicit s: Session): List[RepositoryInfo] = { - (loginAccount match { - // for Administrators - case Some(x) if(x.isAdmin) => Repositories - // for Normal Users - case Some(x) if(!x.isAdmin) => - Repositories filter { t => (t.isPrivate === false.bind) || (t.userName === x.userName) || - (Collaborators.filter { t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName === x.userName.bind)} exists) - } - // for Guests - case None => Repositories filter(_.isPrivate === false.bind) - }).filter { t => - repositoryUserName.map { userName => t.userName === userName.bind } getOrElse LiteralColumn(true) - }.sortBy(_.lastActivityDate desc).list.map{ repository => - new RepositoryInfo( - if(withoutPhysicalInfo){ - new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName, baseUrl) - } else { - JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl) - }, - repository, - getForkedCount( - repository.originUserName.getOrElse(repository.userName), - repository.originRepositoryName.getOrElse(repository.repositoryName) - ), - getRepositoryManagers(repository.userName)) - } - } - - private def getRepositoryManagers(userName: String)(implicit s: Session): Seq[String] = - if(getAccountByUserName(userName).exists(_.isGroupAccount)){ - getGroupMembers(userName).collect { case x if(x.isManager) => x.userName } - } else { - Seq(userName) - } - - /** - * Updates the last activity date of the repository. - */ - def updateLastActivityDate(userName: String, repositoryName: String)(implicit s: Session): Unit = - Repositories.filter(_.byRepository(userName, repositoryName)).map(_.lastActivityDate).update(currentDate) - - /** - * Save repository options. - */ - def saveRepositoryOptions(userName: String, repositoryName: String, - description: Option[String], defaultBranch: String, isPrivate: Boolean)(implicit s: Session): Unit = - Repositories.filter(_.byRepository(userName, repositoryName)) - .map { r => (r.description.?, r.defaultBranch, r.isPrivate, r.updatedDate) } - .update (description, defaultBranch, isPrivate, currentDate) - - /** - * Add collaborator to the repository. - * - * @param userName the user name of the repository owner - * @param repositoryName the repository name - * @param collaboratorName the collaborator name - */ - def addCollaborator(userName: String, repositoryName: String, collaboratorName: String)(implicit s: Session): Unit = - Collaborators insert Collaborator(userName, repositoryName, collaboratorName) - - /** - * Remove collaborator from the repository. - * - * @param userName the user name of the repository owner - * @param repositoryName the repository name - * @param collaboratorName the collaborator name - */ - def removeCollaborator(userName: String, repositoryName: String, collaboratorName: String)(implicit s: Session): Unit = - Collaborators.filter(_.byPrimaryKey(userName, repositoryName, collaboratorName)).delete - - /** - * Remove all collaborators from the repository. - * - * @param userName the user name of the repository owner - * @param repositoryName the repository name - */ - def removeCollaborators(userName: String, repositoryName: String)(implicit s: Session): Unit = - Collaborators.filter(_.byRepository(userName, repositoryName)).delete - - /** - * Returns the list of collaborators name which is sorted with ascending order. - * - * @param userName the user name of the repository owner - * @param repositoryName the repository name - * @return the list of collaborators name - */ - def getCollaborators(userName: String, repositoryName: String)(implicit s: Session): List[String] = - Collaborators.filter(_.byRepository(userName, repositoryName)).sortBy(_.collaboratorName).map(_.collaboratorName).list - - def hasWritePermission(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = { - loginAccount match { - case Some(a) if(a.isAdmin) => true - case Some(a) if(a.userName == owner) => true - case Some(a) if(getCollaborators(owner, repository).contains(a.userName)) => true - case _ => false - } - } - - private def getForkedCount(userName: String, repositoryName: String)(implicit s: Session): Int = - Query(Repositories.filter { t => - (t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind) - }.length).first - - - def getForkedRepositories(userName: String, repositoryName: String)(implicit s: Session): List[(String, String)] = - Repositories.filter { t => - (t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind) - } - .sortBy(_.userName asc).map(t => t.userName -> t.repositoryName).list - -} - -object RepositoryService { - - case class RepositoryInfo(owner: String, name: String, httpUrl: String, repository: Repository, - issueCount: Int, pullCount: Int, commitCount: Int, forkedCount: Int, - branchList: Seq[String], tags: Seq[util.JGitUtil.TagInfo], managers: Seq[String]){ - - lazy val host = """^https?://(.+?)(:\d+)?/""".r.findFirstMatchIn(httpUrl).get.group(1) - - def sshUrl(port: Int, userName: String) = s"ssh://${userName}@${host}:${port}/${owner}/${name}.git" - - /** - * Creates instance with issue count and pull request count. - */ - def this(repo: JGitUtil.RepositoryInfo, model: Repository, issueCount: Int, pullCount: Int, forkedCount: Int, managers: Seq[String]) = - this(repo.owner, repo.name, repo.url, model, issueCount, pullCount, repo.commitCount, forkedCount, repo.branchList, repo.tags, managers) - - /** - * Creates instance without issue count and pull request count. - */ - def this(repo: JGitUtil.RepositoryInfo, model: Repository, forkedCount: Int, managers: Seq[String]) = - this(repo.owner, repo.name, repo.url, model, 0, 0, repo.commitCount, forkedCount, repo.branchList, repo.tags, managers) - } - - case class RepositoryTreeNode(owner: String, name: String, children: List[RepositoryTreeNode]) -} diff --git a/src/main/scala/service/RequestCache.scala b/src/main/scala/service/RequestCache.scala deleted file mode 100644 index 4ff502b..0000000 --- a/src/main/scala/service/RequestCache.scala +++ /dev/null @@ -1,37 +0,0 @@ -package service - -import model.{Account, Issue, Session} -import util.Implicits.request2Session - -/** - * This service is used for a view helper mainly. - * - * It may be called many times in one request, so each method stores - * its result into the cache which available during a request. - */ -trait RequestCache extends SystemSettingsService with AccountService with IssuesService { - - private implicit def context2Session(implicit context: app.Context): Session = - request2Session(context.request) - - def getIssue(userName: String, repositoryName: String, issueId: String) - (implicit context: app.Context): Option[Issue] = { - context.cache(s"issue.${userName}/${repositoryName}#${issueId}"){ - super.getIssue(userName, repositoryName, issueId) - } - } - - def getAccountByUserName(userName: String) - (implicit context: app.Context): Option[Account] = { - context.cache(s"account.${userName}"){ - super.getAccountByUserName(userName) - } - } - - def getAccountByMailAddress(mailAddress: String) - (implicit context: app.Context): Option[Account] = { - context.cache(s"account.${mailAddress}"){ - super.getAccountByMailAddress(mailAddress) - } - } -} diff --git a/src/main/scala/service/SshKeyService.scala b/src/main/scala/service/SshKeyService.scala deleted file mode 100644 index 4446084..0000000 --- a/src/main/scala/service/SshKeyService.scala +++ /dev/null @@ -1,18 +0,0 @@ -package service - -import model.Profile._ -import profile.simple._ -import model.SshKey - -trait SshKeyService { - - def addPublicKey(userName: String, title: String, publicKey: String)(implicit s: Session): Unit = - SshKeys insert SshKey(userName = userName, title = title, publicKey = publicKey) - - def getPublicKeys(userName: String)(implicit s: Session): List[SshKey] = - SshKeys.filter(_.userName === userName.bind).sortBy(_.sshKeyId).list - - def deletePublicKey(userName: String, sshKeyId: Int)(implicit s: Session): Unit = - SshKeys filter (_.byPrimaryKey(userName, sshKeyId)) delete - -} diff --git a/src/main/scala/service/SystemSettingsService.scala b/src/main/scala/service/SystemSettingsService.scala deleted file mode 100644 index 156a23b..0000000 --- a/src/main/scala/service/SystemSettingsService.scala +++ /dev/null @@ -1,213 +0,0 @@ -package service - -import util.Directory._ -import util.ControlUtil._ -import SystemSettingsService._ -import javax.servlet.http.HttpServletRequest - -trait SystemSettingsService { - - def baseUrl(implicit request: HttpServletRequest): String = loadSystemSettings().baseUrl(request) - - def saveSystemSettings(settings: SystemSettings): Unit = { - defining(new java.util.Properties()){ props => - settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", ""))) - settings.information.foreach(x => props.setProperty(Information, x)) - props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString) - props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString) - props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString) - props.setProperty(Gravatar, settings.gravatar.toString) - props.setProperty(Notification, settings.notification.toString) - props.setProperty(Ssh, settings.ssh.toString) - settings.sshPort.foreach(x => props.setProperty(SshPort, x.toString)) - if(settings.notification) { - settings.smtp.foreach { smtp => - props.setProperty(SmtpHost, smtp.host) - smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString)) - smtp.user.foreach(props.setProperty(SmtpUser, _)) - smtp.password.foreach(props.setProperty(SmtpPassword, _)) - smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString)) - smtp.fromAddress.foreach(props.setProperty(SmtpFromAddress, _)) - smtp.fromName.foreach(props.setProperty(SmtpFromName, _)) - } - } - props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString) - if(settings.ldapAuthentication){ - settings.ldap.map { ldap => - props.setProperty(LdapHost, ldap.host) - ldap.port.foreach(x => props.setProperty(LdapPort, x.toString)) - ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x)) - ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x)) - props.setProperty(LdapBaseDN, ldap.baseDN) - props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute) - ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x)) - ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x)) - ldap.mailAttribute.foreach(x => props.setProperty(LdapMailAddressAttribute, x)) - ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString)) - ldap.ssl.foreach(x => props.setProperty(LdapSsl, x.toString)) - ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x)) - } - } - using(new java.io.FileOutputStream(GitBucketConf)){ out => - props.store(out, null) - } - } - } - - - def loadSystemSettings(): SystemSettings = { - defining(new java.util.Properties()){ props => - if(GitBucketConf.exists){ - using(new java.io.FileInputStream(GitBucketConf)){ in => - props.load(in) - } - } - SystemSettings( - getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")), - getOptionValue[String](props, Information, None), - getValue(props, AllowAccountRegistration, false), - getValue(props, AllowAnonymousAccess, true), - getValue(props, IsCreateRepoOptionPublic, true), - getValue(props, Gravatar, true), - getValue(props, Notification, false), - getValue(props, Ssh, false), - getOptionValue(props, SshPort, Some(DefaultSshPort)), - if(getValue(props, Notification, false)){ - Some(Smtp( - getValue(props, SmtpHost, ""), - getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)), - getOptionValue(props, SmtpUser, None), - getOptionValue(props, SmtpPassword, None), - getOptionValue[Boolean](props, SmtpSsl, None), - getOptionValue(props, SmtpFromAddress, None), - getOptionValue(props, SmtpFromName, None))) - } else { - None - }, - getValue(props, LdapAuthentication, false), - if(getValue(props, LdapAuthentication, false)){ - Some(Ldap( - getValue(props, LdapHost, ""), - getOptionValue(props, LdapPort, Some(DefaultLdapPort)), - getOptionValue(props, LdapBindDN, None), - getOptionValue(props, LdapBindPassword, None), - getValue(props, LdapBaseDN, ""), - getValue(props, LdapUserNameAttribute, ""), - getOptionValue(props, LdapAdditionalFilterCondition, None), - getOptionValue(props, LdapFullNameAttribute, None), - getOptionValue(props, LdapMailAddressAttribute, None), - getOptionValue[Boolean](props, LdapTls, None), - getOptionValue[Boolean](props, LdapSsl, None), - getOptionValue(props, LdapKeystore, None))) - } else { - None - } - ) - } - } - -} - -object SystemSettingsService { - import scala.reflect.ClassTag - - case class SystemSettings( - baseUrl: Option[String], - information: Option[String], - allowAccountRegistration: Boolean, - allowAnonymousAccess: Boolean, - isCreateRepoOptionPublic: Boolean, - gravatar: Boolean, - notification: Boolean, - ssh: Boolean, - sshPort: Option[Int], - smtp: Option[Smtp], - ldapAuthentication: Boolean, - ldap: Option[Ldap]){ - def baseUrl(request: HttpServletRequest): String = baseUrl.getOrElse { - defining(request.getRequestURL.toString){ url => - url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length)) - } - }.stripSuffix("/") - } - - case class Ldap( - host: String, - port: Option[Int], - bindDN: Option[String], - bindPassword: Option[String], - baseDN: String, - userNameAttribute: String, - additionalFilterCondition: Option[String], - fullNameAttribute: Option[String], - mailAttribute: Option[String], - tls: Option[Boolean], - ssl: Option[Boolean], - keystore: Option[String]) - - case class Smtp( - host: String, - port: Option[Int], - user: Option[String], - password: Option[String], - ssl: Option[Boolean], - fromAddress: Option[String], - fromName: Option[String]) - - val DefaultSshPort = 29418 - val DefaultSmtpPort = 25 - val DefaultLdapPort = 389 - - private val BaseURL = "base_url" - private val Information = "information" - private val AllowAccountRegistration = "allow_account_registration" - private val AllowAnonymousAccess = "allow_anonymous_access" - private val IsCreateRepoOptionPublic = "is_create_repository_option_public" - private val Gravatar = "gravatar" - private val Notification = "notification" - private val Ssh = "ssh" - private val SshPort = "ssh.port" - private val SmtpHost = "smtp.host" - private val SmtpPort = "smtp.port" - private val SmtpUser = "smtp.user" - private val SmtpPassword = "smtp.password" - private val SmtpSsl = "smtp.ssl" - private val SmtpFromAddress = "smtp.from_address" - private val SmtpFromName = "smtp.from_name" - private val LdapAuthentication = "ldap_authentication" - private val LdapHost = "ldap.host" - private val LdapPort = "ldap.port" - private val LdapBindDN = "ldap.bindDN" - private val LdapBindPassword = "ldap.bind_password" - private val LdapBaseDN = "ldap.baseDN" - private val LdapUserNameAttribute = "ldap.username_attribute" - private val LdapAdditionalFilterCondition = "ldap.additional_filter_condition" - private val LdapFullNameAttribute = "ldap.fullname_attribute" - private val LdapMailAddressAttribute = "ldap.mail_attribute" - private val LdapTls = "ldap.tls" - private val LdapSsl = "ldap.ssl" - private val LdapKeystore = "ldap.keystore" - - private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = - defining(props.getProperty(key)){ value => - if(value == null || value.isEmpty) default - else convertType(value).asInstanceOf[A] - } - - private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] = - defining(props.getProperty(key)){ value => - if(value == null || value.isEmpty) default - else Some(convertType(value)).asInstanceOf[Option[A]] - } - - private def convertType[A: ClassTag](value: String) = - defining(implicitly[ClassTag[A]].runtimeClass){ c => - if(c == classOf[Boolean]) value.toBoolean - else if(c == classOf[Int]) value.toInt - else value - } - -// // TODO temporary flag -// val enablePluginSystem = Option(System.getProperty("enable.plugin")).getOrElse("false").toBoolean - -} diff --git a/src/main/scala/service/WebHookService.scala b/src/main/scala/service/WebHookService.scala deleted file mode 100644 index 83c40ac..0000000 --- a/src/main/scala/service/WebHookService.scala +++ /dev/null @@ -1,271 +0,0 @@ -package service - -import model.Profile._ -import profile.simple._ -import model.{WebHook, Account, Issue, PullRequest, IssueComment, Repository, CommitStatus, CommitState} -import org.slf4j.LoggerFactory -import service.RepositoryService.RepositoryInfo -import util.JGitUtil -import util.JGitUtil.CommitInfo -import org.eclipse.jgit.api.Git -import org.apache.http.message.BasicNameValuePair -import org.apache.http.client.entity.UrlEncodedFormEntity -import org.apache.http.NameValuePair -import java.util.Date -import api._ - -trait WebHookService { - import WebHookService._ - - private val logger = LoggerFactory.getLogger(classOf[WebHookService]) - - def getWebHookURLs(owner: String, repository: String)(implicit s: Session): List[WebHook] = - WebHooks.filter(_.byRepository(owner, repository)).sortBy(_.url).list - - def addWebHookURL(owner: String, repository: String, url :String)(implicit s: Session): Unit = - WebHooks insert WebHook(owner, repository, url) - - def deleteWebHookURL(owner: String, repository: String, url :String)(implicit s: Session): Unit = - WebHooks.filter(_.byPrimaryKey(owner, repository, url)).delete - - def callWebHookOf(owner: String, repository: String, eventName: String)(makePayload: => Option[WebHookPayload])(implicit s: Session, c: JsonFormat.Context): Unit = { - val webHookURLs = getWebHookURLs(owner, repository) - if(webHookURLs.nonEmpty){ - makePayload.map(callWebHook(eventName, webHookURLs, _)) - } - } - - def callWebHook(eventName: String, webHookURLs: List[WebHook], payload: WebHookPayload)(implicit c: JsonFormat.Context): Unit = { - import org.apache.http.client.methods.HttpPost - import org.apache.http.impl.client.HttpClientBuilder - import scala.concurrent._ - import ExecutionContext.Implicits.global - - if(webHookURLs.nonEmpty){ - val json = JsonFormat(payload) - val httpClient = HttpClientBuilder.create.build - - webHookURLs.foreach { webHookUrl => - val f = Future { - logger.debug(s"start web hook invocation for ${webHookUrl}") - val httpPost = new HttpPost(webHookUrl.url) - httpPost.addHeader("X-Github-Event", eventName) - - val params: java.util.List[NameValuePair] = new java.util.ArrayList() - params.add(new BasicNameValuePair("payload", json)) - httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")) - - httpClient.execute(httpPost) - httpPost.releaseConnection() - logger.debug(s"end web hook invocation for ${webHookUrl}") - } - f.onSuccess { - case s => logger.debug(s"Success: web hook request to ${webHookUrl.url}") - } - f.onFailure { - case t => logger.error(s"Failed: web hook request to ${webHookUrl.url}", t) - } - } - } - logger.debug("end callWebHook") - } -} - - -trait WebHookPullRequestService extends WebHookService { - self: AccountService with RepositoryService with PullRequestService with IssuesService => - - import WebHookService._ - // https://developer.github.com/v3/activity/events/types/#issuesevent - def callIssuesWebHook(action: String, repository: RepositoryService.RepositoryInfo, issue: Issue, baseUrl: String, sender: model.Account)(implicit s: Session, context:JsonFormat.Context): Unit = { - callWebHookOf(repository.owner, repository.name, "issues"){ - val users = getAccountsByUserNames(Set(repository.owner, issue.userName), Set(sender)) - for{ - repoOwner <- users.get(repository.owner) - issueUser <- users.get(issue.userName) - } yield { - WebHookIssuesPayload( - action = action, - number = issue.issueId, - repository = ApiRepository(repository, ApiUser(repoOwner)), - issue = ApiIssue(issue, ApiUser(issueUser)), - sender = ApiUser(sender)) - } - } - } - - def callPullRequestWebHook(action: String, repository: RepositoryService.RepositoryInfo, issueId: Int, baseUrl: String, sender: model.Account)(implicit s: Session, context:JsonFormat.Context): Unit = { - import WebHookService._ - callWebHookOf(repository.owner, repository.name, "pull_request"){ - for{ - (issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId) - users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName), Set(sender)) - baseOwner <- users.get(repository.owner) - headOwner <- users.get(pullRequest.requestUserName) - headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName, baseUrl) - } yield { - WebHookPullRequestPayload( - action = action, - issue = issue, - pullRequest = pullRequest, - headRepository = headRepo, - headOwner = headOwner, - baseRepository = repository, - baseOwner = baseOwner, - sender = sender) - } - } - } - - def getPullRequestsByRequestForWebhook(userName:String, repositoryName:String, branch:String) - (implicit s: Session): Map[(Issue, PullRequest, Account, Account), List[WebHook]] = - (for{ - is <- Issues if is.closed === false.bind - pr <- PullRequests if pr.byPrimaryKey(is.userName, is.repositoryName, is.issueId) - if pr.requestUserName === userName.bind - if pr.requestRepositoryName === repositoryName.bind - if pr.requestBranch === branch.bind - bu <- Accounts if bu.userName === pr.userName - ru <- Accounts if ru.userName === pr.requestUserName - wh <- WebHooks if wh.byRepository(is.userName , is.repositoryName) - } yield { - ((is, pr, bu, ru), wh) - }).list.groupBy(_._1).mapValues(_.map(_._2)) - - def callPullRequestWebHookByRequestBranch(action: String, requestRepository: RepositoryService.RepositoryInfo, requestBranch: String, baseUrl: String, sender: model.Account)(implicit s: Session, context:JsonFormat.Context): Unit = { - import WebHookService._ - for{ - ((issue, pullRequest, baseOwner, headOwner), webHooks) <- getPullRequestsByRequestForWebhook(requestRepository.owner, requestRepository.name, requestBranch) - baseRepo <- getRepository(pullRequest.userName, pullRequest.repositoryName, baseUrl) - } yield { - val payload = WebHookPullRequestPayload( - action = action, - issue = issue, - pullRequest = pullRequest, - headRepository = requestRepository, - headOwner = headOwner, - baseRepository = baseRepo, - baseOwner = baseOwner, - sender = sender) - callWebHook("pull_request", webHooks, payload) - } - } -} - -trait WebHookIssueCommentService extends WebHookPullRequestService { - self: AccountService with RepositoryService with PullRequestService with IssuesService => - - import WebHookService._ - def callIssueCommentWebHook(repository: RepositoryService.RepositoryInfo, issue: Issue, issueCommentId: Int, sender: model.Account)(implicit s: Session, context:JsonFormat.Context): Unit = { - callWebHookOf(repository.owner, repository.name, "issue_comment"){ - for{ - issueComment <- getComment(repository.owner, repository.name, issueCommentId.toString()) - users = getAccountsByUserNames(Set(issue.userName, repository.owner, issueComment.userName), Set(sender)) - issueUser <- users.get(issue.userName) - repoOwner <- users.get(repository.owner) - commenter <- users.get(issueComment.userName) - } yield { - WebHookIssueCommentPayload( - issue = issue, - issueUser = issueUser, - comment = issueComment, - commentUser = commenter, - repository = repository, - repositoryUser = repoOwner, - sender = sender) - } - } - } -} - -object WebHookService { - trait WebHookPayload - - // https://developer.github.com/v3/activity/events/types/#pushevent - case class WebHookPushPayload( - pusher: ApiUser, - ref: String, - commits: List[ApiCommit], - repository: ApiRepository - ) extends WebHookPayload - - object WebHookPushPayload { - def apply(git: Git, pusher: Account, refName: String, repositoryInfo: RepositoryInfo, - commits: List[CommitInfo], repositoryOwner: Account): WebHookPushPayload = - WebHookPushPayload( - ApiUser(pusher), - refName, - commits.map{ commit => ApiCommit(git, util.RepositoryName(repositoryInfo), commit) }, - ApiRepository( - repositoryInfo, - owner= ApiUser(repositoryOwner) - ) - ) - } - - // https://developer.github.com/v3/activity/events/types/#issuesevent - case class WebHookIssuesPayload( - action: String, - number: Int, - repository: ApiRepository, - issue: ApiIssue, - sender: ApiUser) extends WebHookPayload - - // https://developer.github.com/v3/activity/events/types/#pullrequestevent - case class WebHookPullRequestPayload( - action: String, - number: Int, - repository: ApiRepository, - pull_request: ApiPullRequest, - sender: ApiUser - ) extends WebHookPayload - - object WebHookPullRequestPayload{ - def apply(action: String, - issue: Issue, - pullRequest: PullRequest, - headRepository: RepositoryInfo, - headOwner: Account, - baseRepository: RepositoryInfo, - baseOwner: Account, - sender: model.Account): WebHookPullRequestPayload = { - val headRepoPayload = ApiRepository(headRepository, headOwner) - val baseRepoPayload = ApiRepository(baseRepository, baseOwner) - val senderPayload = ApiUser(sender) - val pr = ApiPullRequest(issue, pullRequest, headRepoPayload, baseRepoPayload, senderPayload) - WebHookPullRequestPayload( - action = action, - number = issue.issueId, - repository = pr.base.repo, - pull_request = pr, - sender = senderPayload - ) - } - } - - // https://developer.github.com/v3/activity/events/types/#issuecommentevent - case class WebHookIssueCommentPayload( - action: String, - repository: ApiRepository, - issue: ApiIssue, - comment: ApiComment, - sender: ApiUser - ) extends WebHookPayload - - object WebHookIssueCommentPayload{ - def apply( - issue: Issue, - issueUser: Account, - comment: IssueComment, - commentUser: Account, - repository: RepositoryInfo, - repositoryUser: Account, - sender: Account): WebHookIssueCommentPayload = - WebHookIssueCommentPayload( - action = "created", - repository = ApiRepository(repository, repositoryUser), - issue = ApiIssue(issue, ApiUser(issueUser)), - comment = ApiComment(comment, ApiUser(commentUser)), - sender = ApiUser(sender)) - } -} diff --git a/src/main/scala/service/WikiService.scala b/src/main/scala/service/WikiService.scala deleted file mode 100644 index add5021..0000000 --- a/src/main/scala/service/WikiService.scala +++ /dev/null @@ -1,281 +0,0 @@ -package service - -import java.util.Date -import org.eclipse.jgit.api.Git -import util._ -import _root_.util.ControlUtil._ -import org.eclipse.jgit.treewalk.CanonicalTreeParser -import org.eclipse.jgit.lib._ -import org.eclipse.jgit.dircache.DirCache -import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter} -import java.io.ByteArrayInputStream -import org.eclipse.jgit.patch._ -import org.eclipse.jgit.api.errors.PatchFormatException -import scala.collection.JavaConverters._ -import service.RepositoryService.RepositoryInfo - -object WikiService { - - /** - * The model for wiki page. - * - * @param name the page name - * @param content the page content - * @param committer the last committer - * @param time the last modified time - * @param id the latest commit id - */ - case class WikiPageInfo(name: String, content: String, committer: String, time: Date, id: String) - - /** - * The model for wiki page history. - * - * @param name the page name - * @param committer the committer the committer - * @param message the commit message - * @param date the commit date - */ - case class WikiPageHistoryInfo(name: String, committer: String, message: String, date: Date) - - def httpUrl(repository: RepositoryInfo) = repository.httpUrl.replaceFirst("\\.git\\Z", ".wiki.git") - - def sshUrl(repository: RepositoryInfo, settings: SystemSettingsService.SystemSettings, userName: String) = - repository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), userName).replaceFirst("\\.git\\Z", ".wiki.git") -} - -trait WikiService { - import WikiService._ - - def createWikiRepository(loginAccount: model.Account, owner: String, repository: String): Unit = - LockUtil.lock(s"${owner}/${repository}/wiki"){ - defining(Directory.getWikiRepositoryDir(owner, repository)){ dir => - if(!dir.exists){ - JGitUtil.initRepository(dir) - saveWikiPage(owner, repository, "Home", "Home", s"Welcome to the ${repository} wiki!!", loginAccount, "Initial Commit", None) - } - } - } - - /** - * Returns the wiki page. - */ - def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = { - using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - if(!JGitUtil.isEmpty(git)){ - JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file => - WikiPageInfo(file.name, StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes), - file.author, file.time, file.commitId) - } - } else None - } - } - - /** - * Returns the content of the specified file. - */ - def getFileContent(owner: String, repository: String, path: String): Option[Array[Byte]] = - using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - if(!JGitUtil.isEmpty(git)){ - val index = path.lastIndexOf('/') - val parentPath = if(index < 0) "." else path.substring(0, index) - val fileName = if(index < 0) path else path.substring(index + 1) - - JGitUtil.getFileList(git, "master", parentPath).find(_.name == fileName).map { file => - git.getRepository.open(file.id).getBytes - } - } else None - } - - /** - * Returns the list of wiki page names. - */ - def getWikiPageList(owner: String, repository: String): List[String] = { - using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - JGitUtil.getFileList(git, "master", ".") - .filter(_.name.endsWith(".md")) - .map(_.name.stripSuffix(".md")) - .sortBy(x => x) - } - } - - /** - * Reverts specified changes. - */ - def revertWikiPage(owner: String, repository: String, from: String, to: String, - committer: model.Account, pageName: Option[String]): Boolean = { - - case class RevertInfo(operation: String, filePath: String, source: String) - - try { - LockUtil.lock(s"${owner}/${repository}/wiki"){ - using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - - val reader = git.getRepository.newObjectReader - val oldTreeIter = new CanonicalTreeParser - oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}")) - - val newTreeIter = new CanonicalTreeParser - newTreeIter.reset(reader, git.getRepository.resolve(to + "^{tree}")) - - val diffs = git.diff.setNewTree(oldTreeIter).setOldTree(newTreeIter).call.asScala.filter { diff => - pageName match { - case Some(x) => diff.getNewPath == x + ".md" - case None => true - } - } - - val patch = using(new java.io.ByteArrayOutputStream()){ out => - val formatter = new DiffFormatter(out) - formatter.setRepository(git.getRepository) - formatter.format(diffs.asJava) - new String(out.toByteArray, "UTF-8") - } - - val p = new Patch() - p.parse(new ByteArrayInputStream(patch.getBytes("UTF-8"))) - if(!p.getErrors.isEmpty){ - throw new PatchFormatException(p.getErrors()) - } - val revertInfo = (p.getFiles.asScala.map { fh => - fh.getChangeType match { - case DiffEntry.ChangeType.MODIFY => { - val source = getWikiPage(owner, repository, fh.getNewPath.stripSuffix(".md")).map(_.content).getOrElse("") - val applied = PatchUtil.apply(source, patch, fh) - if(applied != null){ - Seq(RevertInfo("ADD", fh.getNewPath, applied)) - } else Nil - } - case DiffEntry.ChangeType.ADD => { - val applied = PatchUtil.apply("", patch, fh) - if(applied != null){ - Seq(RevertInfo("ADD", fh.getNewPath, applied)) - } else Nil - } - case DiffEntry.ChangeType.DELETE => { - Seq(RevertInfo("DELETE", fh.getNewPath, "")) - } - case DiffEntry.ChangeType.RENAME => { - val applied = PatchUtil.apply("", patch, fh) - if(applied != null){ - Seq(RevertInfo("DELETE", fh.getOldPath, ""), RevertInfo("ADD", fh.getNewPath, applied)) - } else { - Seq(RevertInfo("DELETE", fh.getOldPath, "")) - } - } - case _ => Nil - } - }).flatten - - if(revertInfo.nonEmpty){ - val builder = DirCache.newInCore.builder() - val inserter = git.getRepository.newObjectInserter() - val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") - - JGitUtil.processTree(git, headId){ (path, tree) => - if(revertInfo.find(x => x.filePath == path).isEmpty){ - builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) - } - } - - revertInfo.filter(_.operation == "ADD").foreach { x => - builder.add(JGitUtil.createDirCacheEntry(x.filePath, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, x.source.getBytes("UTF-8")))) - } - builder.finish() - - JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), - Constants.HEAD, committer.fullName, committer.mailAddress, - pageName match { - case Some(x) => s"Revert ${from} ... ${to} on ${x}" - case None => s"Revert ${from} ... ${to}" - }) - } - } - } - true - } catch { - case e: Exception => { - e.printStackTrace() - false - } - } - } - - /** - * Save the wiki page. - */ - def saveWikiPage(owner: String, repository: String, currentPageName: String, newPageName: String, - content: String, committer: model.Account, message: String, currentId: Option[String]): Option[String] = { - LockUtil.lock(s"${owner}/${repository}/wiki"){ - using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - val builder = DirCache.newInCore.builder() - val inserter = git.getRepository.newObjectInserter() - val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") - var created = true - var updated = false - var removed = false - - if(headId != null){ - JGitUtil.processTree(git, headId){ (path, tree) => - if(path == currentPageName + ".md" && currentPageName != newPageName){ - removed = true - } else if(path != newPageName + ".md"){ - builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) - } else { - created = false - updated = JGitUtil.getContentFromId(git, tree.getEntryObjectId, true).map(new String(_, "UTF-8") != content).getOrElse(false) - } - } - } - - if(created || updated || removed){ - builder.add(JGitUtil.createDirCacheEntry(newPageName + ".md", FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8")))) - builder.finish() - val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), - Constants.HEAD, committer.fullName, committer.mailAddress, - if(message.trim.length == 0) { - if(removed){ - s"Rename ${currentPageName} to ${newPageName}" - } else if(created){ - s"Created ${newPageName}" - } else { - s"Updated ${newPageName}" - } - } else { - message - }) - - Some(newHeadId.getName) - } else None - } - } - } - - /** - * Delete the wiki page. - */ - def deleteWikiPage(owner: String, repository: String, pageName: String, - committer: String, mailAddress: String, message: String): Unit = { - LockUtil.lock(s"${owner}/${repository}/wiki"){ - using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - val builder = DirCache.newInCore.builder() - val inserter = git.getRepository.newObjectInserter() - val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") - var removed = false - - JGitUtil.processTree(git, headId){ (path, tree) => - if(path != pageName + ".md"){ - builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) - } else { - removed = true - } - } - if(removed){ - builder.finish() - JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), - Constants.HEAD, committer, mailAddress, message) - } - } - } - } - -} diff --git a/src/main/scala/servlet/AccessTokenAuthenticationFilter.scala b/src/main/scala/servlet/AccessTokenAuthenticationFilter.scala deleted file mode 100644 index 56e50c2..0000000 --- a/src/main/scala/servlet/AccessTokenAuthenticationFilter.scala +++ /dev/null @@ -1,41 +0,0 @@ -package servlet - -import javax.servlet._ -import javax.servlet.http.{HttpServletRequest, HttpServletResponse} - -import service.AccessTokenService -import util.Keys -import org.scalatra.servlet.ServletApiImplicits._ -import model.Account -import org.scalatra._ - -class AccessTokenAuthenticationFilter extends Filter with AccessTokenService { - private val tokenHeaderPrefix = "token " - - override def init(filterConfig: FilterConfig): Unit = {} - - override def destroy(): Unit = {} - - override def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = { - implicit val request = req.asInstanceOf[HttpServletRequest] - implicit val session = req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session] - val response = res.asInstanceOf[HttpServletResponse] - Option(request.getHeader("Authorization")).map{ - case auth if auth.startsWith("token ") => AccessTokenService.getAccountByAccessToken(auth.substring(6).trim).toRight(Unit) - // TODO Basic Authentication Support - case _ => Left(Unit) - }.orElse{ - Option(request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]).map(Right(_)) - } match { - case Some(Right(account)) => request.setAttribute(Keys.Session.LoginAccount, account); chain.doFilter(req, res) - case None => chain.doFilter(req, res) - case Some(Left(_)) => { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED) - response.setContentType("Content-Type: application/json; charset=utf-8") - val w = response.getWriter() - w.print("""{ "message": "Bad credentials" }""") - w.close() - } - } - } -} diff --git a/src/main/scala/servlet/BasicAuthenticationFilter.scala b/src/main/scala/servlet/BasicAuthenticationFilter.scala deleted file mode 100644 index cbecfc1..0000000 --- a/src/main/scala/servlet/BasicAuthenticationFilter.scala +++ /dev/null @@ -1,92 +0,0 @@ -package servlet - -import javax.servlet._ -import javax.servlet.http._ -import service.{SystemSettingsService, AccountService, RepositoryService} -import model._ -import org.slf4j.LoggerFactory -import util.Implicits._ -import util.ControlUtil._ -import util.Keys - -/** - * Provides BASIC Authentication for [[servlet.GitRepositoryServlet]]. - */ -class BasicAuthenticationFilter extends Filter with RepositoryService with AccountService with SystemSettingsService { - - private val logger = LoggerFactory.getLogger(classOf[BasicAuthenticationFilter]) - - def init(config: FilterConfig) = {} - - def destroy(): Unit = {} - - def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = { - implicit val request = req.asInstanceOf[HttpServletRequest] - val response = res.asInstanceOf[HttpServletResponse] - - val wrappedResponse = new HttpServletResponseWrapper(response){ - override def setCharacterEncoding(encoding: String) = {} - } - - val isUpdating = request.getRequestURI.endsWith("/git-receive-pack") || "service=git-receive-pack".equals(request.getQueryString) - - val settings = loadSystemSettings() - - try { - defining(request.paths){ - case Array(_, repositoryOwner, repositoryName, _*) => - getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", ""), "") match { - case Some(repository) => { - if(!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess){ - chain.doFilter(req, wrappedResponse) - } else { - request.getHeader("Authorization") match { - case null => requireAuth(response) - case auth => decodeAuthHeader(auth).split(":") match { - case Array(username, password) => { - authenticate(settings, username, password) match { - case Some(account) => { - if(isUpdating && hasWritePermission(repository.owner, repository.name, Some(account))){ - request.setAttribute(Keys.Request.UserName, account.userName) - } - chain.doFilter(req, wrappedResponse) - } - case None => requireAuth(response) - } - } - case _ => requireAuth(response) - } - } - } - } - case None => { - logger.debug(s"Repository ${repositoryOwner}/${repositoryName} is not found.") - response.sendError(HttpServletResponse.SC_NOT_FOUND) - } - } - case _ => { - logger.debug(s"Not enough path arguments: ${request.paths}") - response.sendError(HttpServletResponse.SC_NOT_FOUND) - } - } - } catch { - case ex: Exception => { - logger.error("error", ex) - requireAuth(response) - } - } - } - - private def requireAuth(response: HttpServletResponse): Unit = { - response.setHeader("WWW-Authenticate", "BASIC realm=\"GitBucket\"") - response.sendError(HttpServletResponse.SC_UNAUTHORIZED) - } - - private def decodeAuthHeader(header: String): String = { - try { - new String(new sun.misc.BASE64Decoder().decodeBuffer(header.substring(6))) - } catch { - case _: Throwable => "" - } - } -} \ No newline at end of file diff --git a/src/main/scala/servlet/GitRepositoryServlet.scala b/src/main/scala/servlet/GitRepositoryServlet.scala deleted file mode 100644 index 9ebb4ac..0000000 --- a/src/main/scala/servlet/GitRepositoryServlet.scala +++ /dev/null @@ -1,217 +0,0 @@ -package servlet - -import org.eclipse.jgit.http.server.GitServlet -import org.eclipse.jgit.lib._ -import org.eclipse.jgit.transport._ -import org.eclipse.jgit.transport.resolver._ -import org.slf4j.LoggerFactory - -import javax.servlet.ServletConfig -import javax.servlet.ServletContext -import javax.servlet.http.{HttpServletResponse, HttpServletRequest} -import util.{StringUtil, Keys, JGitUtil, Directory} -import util.ControlUtil._ -import util.Implicits._ -import service._ -import WebHookService._ -import org.eclipse.jgit.api.Git -import util.JGitUtil.CommitInfo -import service.IssuesService.IssueSearchCondition -import model.Session - -/** - * Provides Git repository via HTTP. - * - * This servlet provides only Git repository functionality. - * Authentication is provided by [[servlet.BasicAuthenticationFilter]]. - */ -class GitRepositoryServlet extends GitServlet with SystemSettingsService { - - private val logger = LoggerFactory.getLogger(classOf[GitRepositoryServlet]) - - override def init(config: ServletConfig): Unit = { - setReceivePackFactory(new GitBucketReceivePackFactory()) - - // TODO are there any other ways...? - super.init(new ServletConfig(){ - def getInitParameter(name: String): String = name match { - case "base-path" => Directory.RepositoryHome - case "export-all" => "true" - case name => config.getInitParameter(name) - } - def getInitParameterNames(): java.util.Enumeration[String] = { - config.getInitParameterNames - } - - def getServletContext(): ServletContext = config.getServletContext - def getServletName(): String = config.getServletName - }) - - super.init(config) - } - - override def service(req: HttpServletRequest, res: HttpServletResponse): Unit = { - val agent = req.getHeader("USER-AGENT") - val index = req.getRequestURI.indexOf(".git") - if(index >= 0 && (agent == null || agent.toLowerCase.indexOf("git/") < 0)){ - // redirect for browsers - val paths = req.getRequestURI.substring(0, index).split("/") - res.sendRedirect(baseUrl(req) + "/" + paths.dropRight(1).last + "/" + paths.last) - } else { - // response for git client - super.service(req, res) - } - } -} - -class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest] with SystemSettingsService { - - private val logger = LoggerFactory.getLogger(classOf[GitBucketReceivePackFactory]) - - override def create(request: HttpServletRequest, db: Repository): ReceivePack = { - val receivePack = new ReceivePack(db) - val pusher = request.getAttribute(Keys.Request.UserName).asInstanceOf[String] - - logger.debug("requestURI: " + request.getRequestURI) - logger.debug("pusher:" + pusher) - - defining(request.paths){ paths => - val owner = paths(1) - val repository = paths(2).stripSuffix(".git") - - logger.debug("repository:" + owner + "/" + repository) - - if(!repository.endsWith(".wiki")){ - defining(request) { implicit r => - val hook = new CommitLogHook(owner, repository, pusher, baseUrl) - receivePack.setPreReceiveHook(hook) - receivePack.setPostReceiveHook(hook) - } - } - receivePack - } - } -} - -import scala.collection.JavaConverters._ - -class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String)(implicit session: Session) - extends PostReceiveHook with PreReceiveHook - with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService - with WebHookPullRequestService { - - private val logger = LoggerFactory.getLogger(classOf[CommitLogHook]) - private var existIds: Seq[String] = Nil - - def onPreReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = { - try { - using(Git.open(Directory.getRepositoryDir(owner, repository))) { git => - existIds = JGitUtil.getAllCommitIds(git) - } - } catch { - case ex: Exception => { - logger.error(ex.toString, ex) - throw ex - } - } - } - - def onPostReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = { - try { - using(Git.open(Directory.getRepositoryDir(owner, repository))) { git => - val pushedIds = scala.collection.mutable.Set[String]() - commands.asScala.foreach { command => - logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}") - implicit val apiContext = api.JsonFormat.Context(baseUrl) - val refName = command.getRefName.split("/") - val branchName = refName.drop(2).mkString("/") - val commits = if (refName(1) == "tags") { - Nil - } else { - command.getType match { - case ReceiveCommand.Type.DELETE => Nil - case _ => JGitUtil.getCommitLog(git, command.getOldId.name, command.getNewId.name) - } - } - - // Retrieve all issue count in the repository - val issueCount = - countIssue(IssueSearchCondition(state = "open"), false, owner -> repository) + - countIssue(IssueSearchCondition(state = "closed"), false, owner -> repository) - - val repositoryInfo = getRepository(owner, repository, baseUrl).get - - // Extract new commit and apply issue comment - val defaultBranch = repositoryInfo.repository.defaultBranch - val newCommits = commits.flatMap { commit => - if (!existIds.contains(commit.id) && !pushedIds.contains(commit.id)) { - if (issueCount > 0) { - pushedIds.add(commit.id) - createIssueComment(commit) - // close issues - if(refName(1) == "heads" && branchName == defaultBranch && command.getType == ReceiveCommand.Type.UPDATE){ - closeIssuesFromMessage(commit.fullMessage, pusher, owner, repository) - } - } - Some(commit) - } else None - } - - // record activity - if(refName(1) == "heads"){ - command.getType match { - case ReceiveCommand.Type.CREATE => recordCreateBranchActivity(owner, repository, pusher, branchName) - case ReceiveCommand.Type.UPDATE => recordPushActivity(owner, repository, pusher, branchName, newCommits) - case ReceiveCommand.Type.DELETE => recordDeleteBranchActivity(owner, repository, pusher, branchName) - case _ => - } - } else if(refName(1) == "tags"){ - command.getType match { - case ReceiveCommand.Type.CREATE => recordCreateTagActivity(owner, repository, pusher, branchName, newCommits) - case ReceiveCommand.Type.DELETE => recordDeleteTagActivity(owner, repository, pusher, branchName, newCommits) - case _ => - } - } - - if(refName(1) == "heads"){ - command.getType match { - case ReceiveCommand.Type.CREATE | - ReceiveCommand.Type.UPDATE | - ReceiveCommand.Type.UPDATE_NONFASTFORWARD => - updatePullRequests(owner, repository, branchName) - getAccountByUserName(pusher).map{ pusherAccount => - callPullRequestWebHookByRequestBranch("synchronize", repositoryInfo, branchName, baseUrl, pusherAccount) - } - case _ => - } - } - - // call web hook - callWebHookOf(owner, repository, "push"){ - for(pusherAccount <- getAccountByUserName(pusher); - ownerAccount <- getAccountByUserName(owner)) yield { - WebHookPushPayload(git, pusherAccount, command.getRefName, repositoryInfo, newCommits, ownerAccount) - } - } - } - } - // update repository last modified time. - updateLastActivityDate(owner, repository) - } catch { - case ex: Exception => { - logger.error(ex.toString, ex) - throw ex - } - } - } - - private def createIssueComment(commit: CommitInfo) = { - StringUtil.extractIssueId(commit.fullMessage).foreach { issueId => - if(getIssue(owner, repository, issueId).isDefined){ - getAccountByMailAddress(commit.committerEmailAddress).foreach { account => - createComment(owner, repository, account.userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit") - } - } - } - } -} diff --git a/src/main/scala/servlet/InitializeListener.scala b/src/main/scala/servlet/InitializeListener.scala deleted file mode 100644 index a4b1e1e..0000000 --- a/src/main/scala/servlet/InitializeListener.scala +++ /dev/null @@ -1,205 +0,0 @@ -package servlet - -import java.io.File -import java.sql.{DriverManager, Connection} -import org.apache.commons.io.FileUtils -import javax.servlet.{ServletContextListener, ServletContextEvent} -import org.slf4j.LoggerFactory -import util.Directory._ -import util.ControlUtil._ -import util.JDBCUtil._ -import org.eclipse.jgit.api.Git -import util.{Version, Versions} -import plugin._ -import util.{DatabaseConfig, Directory} - -object AutoUpdate { - - /** - * The history of versions. A head of this sequence is the current BitBucket version. - */ - val versions = Seq( - new Version(2, 9), - new Version(2, 8), - new Version(2, 7) { - override def update(conn: Connection, cl: ClassLoader): Unit = { - super.update(conn, cl) - conn.select("SELECT * FROM REPOSITORY"){ rs => - // Rename attached files directory from /issues to /comments - val userName = rs.getString("USER_NAME") - val repoName = rs.getString("REPOSITORY_NAME") - defining(Directory.getAttachedDir(userName, repoName)){ newDir => - val oldDir = new File(newDir.getParentFile, "issues") - if(oldDir.exists && oldDir.isDirectory){ - oldDir.renameTo(newDir) - } - } - // Update ORIGIN_USER_NAME and ORIGIN_REPOSITORY_NAME if it does not exist - val originalUserName = rs.getString("ORIGIN_USER_NAME") - val originalRepoName = rs.getString("ORIGIN_REPOSITORY_NAME") - if(originalUserName != null && originalRepoName != null){ - if(conn.selectInt("SELECT COUNT(*) FROM REPOSITORY WHERE USER_NAME = ? AND REPOSITORY_NAME = ?", - originalUserName, originalRepoName) == 0){ - conn.update("UPDATE REPOSITORY SET ORIGIN_USER_NAME = NULL, ORIGIN_REPOSITORY_NAME = NULL " + - "WHERE USER_NAME = ? AND REPOSITORY_NAME = ?", userName, repoName) - } - } - // Update PARENT_USER_NAME and PARENT_REPOSITORY_NAME if it does not exist - val parentUserName = rs.getString("PARENT_USER_NAME") - val parentRepoName = rs.getString("PARENT_REPOSITORY_NAME") - if(parentUserName != null && parentRepoName != null){ - if(conn.selectInt("SELECT COUNT(*) FROM REPOSITORY WHERE USER_NAME = ? AND REPOSITORY_NAME = ?", - parentUserName, parentRepoName) == 0){ - conn.update("UPDATE REPOSITORY SET PARENT_USER_NAME = NULL, PARENT_REPOSITORY_NAME = NULL " + - "WHERE USER_NAME = ? AND REPOSITORY_NAME = ?", userName, repoName) - } - } - } - } - }, - new Version(2, 6), - new Version(2, 5), - new Version(2, 4), - new Version(2, 3) { - override def update(conn: Connection, cl: ClassLoader): Unit = { - super.update(conn, cl) - conn.select("SELECT ACTIVITY_ID, ADDITIONAL_INFO FROM ACTIVITY WHERE ACTIVITY_TYPE='push'"){ rs => - val curInfo = rs.getString("ADDITIONAL_INFO") - val newInfo = curInfo.split("\n").filter(_ matches "^[0-9a-z]{40}:.*").mkString("\n") - if (curInfo != newInfo) { - conn.update("UPDATE ACTIVITY SET ADDITIONAL_INFO = ? WHERE ACTIVITY_ID = ?", newInfo, rs.getInt("ACTIVITY_ID")) - } - } - ignore { - FileUtils.deleteDirectory(Directory.getPluginCacheDir()) - //FileUtils.deleteDirectory(new File(Directory.PluginHome)) - } - } - }, - new Version(2, 2), - new Version(2, 1), - new Version(2, 0){ - override def update(conn: Connection, cl: ClassLoader): Unit = { - import eu.medsea.mimeutil.{MimeUtil2, MimeType} - - val mimeUtil = new MimeUtil2() - mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector") - - super.update(conn, cl) - conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs => - defining(Directory.getAttachedDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))){ dir => - if(dir.exists && dir.isDirectory){ - dir.listFiles.foreach { file => - if(file.getName.indexOf('.') < 0){ - val mimeType = MimeUtil2.getMostSpecificMimeType(mimeUtil.getMimeTypes(file, new MimeType("application/octet-stream"))).toString - if(mimeType.startsWith("image/")){ - file.renameTo(new File(file.getParent, file.getName + "." + mimeType.split("/")(1))) - } - } - } - } - } - } - } - }, - Version(1, 13), - Version(1, 12), - Version(1, 11), - Version(1, 10), - Version(1, 9), - Version(1, 8), - Version(1, 7), - Version(1, 6), - Version(1, 5), - Version(1, 4), - new Version(1, 3){ - override def update(conn: Connection, cl: ClassLoader): Unit = { - super.update(conn, cl) - // Fix wiki repository configuration - conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs => - using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git => - defining(git.getRepository.getConfig){ config => - if(!config.getBoolean("http", "receivepack", false)){ - config.setBoolean("http", null, "receivepack", true) - config.save - } - } - } - } - } - }, - Version(1, 2), - Version(1, 1), - Version(1, 0), - Version(0, 0) - ) - - /** - * The head version of BitBucket. - */ - val headVersion = versions.head - - /** - * The version file (GITBUCKET_HOME/version). - */ - lazy val versionFile = new File(GitBucketHome, "version") - - /** - * Returns the current version from the version file. - */ - def getCurrentVersion(): Version = { - if(versionFile.exists){ - FileUtils.readFileToString(versionFile, "UTF-8").trim.split("\\.") match { - case Array(majorVersion, minorVersion) => { - versions.find { v => - v.majorVersion == majorVersion.toInt && v.minorVersion == minorVersion.toInt - }.getOrElse(Version(0, 0)) - } - case _ => Version(0, 0) - } - } else Version(0, 0) - } - -} - -/** - * Initialize GitBucket system. - * Update database schema and load plug-ins automatically in the context initializing. - */ -class InitializeListener extends ServletContextListener { - import AutoUpdate._ - - private val logger = LoggerFactory.getLogger(classOf[InitializeListener]) - - override def contextInitialized(event: ServletContextEvent): Unit = { - val dataDir = event.getServletContext.getInitParameter("gitbucket.home") - if(dataDir != null){ - System.setProperty("gitbucket.home", dataDir) - } - org.h2.Driver.load() - - defining(getConnection()){ conn => - // Migration - logger.debug("Start schema update") - Versions.update(conn, headVersion, getCurrentVersion(), versions, Thread.currentThread.getContextClassLoader){ conn => - FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8") - } - // Load plugins - logger.debug("Initialize plugins") - PluginRegistry.initialize(event.getServletContext, conn) - } - - } - - def contextDestroyed(event: ServletContextEvent): Unit = { - // Shutdown plugins - PluginRegistry.shutdown(event.getServletContext) - } - - private def getConnection(): Connection = - DriverManager.getConnection( - DatabaseConfig.url, - DatabaseConfig.user, - DatabaseConfig.password) - -} diff --git a/src/main/scala/servlet/SessionCleanupListener.scala b/src/main/scala/servlet/SessionCleanupListener.scala deleted file mode 100644 index ee4ea7b..0000000 --- a/src/main/scala/servlet/SessionCleanupListener.scala +++ /dev/null @@ -1,16 +0,0 @@ -package servlet - -import javax.servlet.http.{HttpSessionEvent, HttpSessionListener} -import org.apache.commons.io.FileUtils -import util.Directory._ - -/** - * Removes session associated temporary files when session is destroyed. - */ -class SessionCleanupListener extends HttpSessionListener { - - def sessionCreated(se: HttpSessionEvent): Unit = {} - - def sessionDestroyed(se: HttpSessionEvent): Unit = FileUtils.deleteDirectory(getTemporaryDir(se.getSession.getId)) - -} diff --git a/src/main/scala/servlet/TransactionFilter.scala b/src/main/scala/servlet/TransactionFilter.scala deleted file mode 100644 index 20d37f5..0000000 --- a/src/main/scala/servlet/TransactionFilter.scala +++ /dev/null @@ -1,59 +0,0 @@ -package servlet - -import javax.servlet._ -import javax.servlet.http.HttpServletRequest -import com.mchange.v2.c3p0.ComboPooledDataSource -import org.slf4j.LoggerFactory -import slick.jdbc.JdbcBackend.{Database => SlickDatabase, Session} -import util.{DatabaseConfig, 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() withTransaction { session => - logger.debug("begin transaction") - req.setAttribute(Keys.Request.DBSession, session) - chain.doFilter(req, res) - logger.debug("end transaction") - } - } - } - -} - -object Database { - - private val logger = LoggerFactory.getLogger(Database.getClass) - - private val db: SlickDatabase = { - val datasource = new ComboPooledDataSource - - datasource.setDriverClass(DatabaseConfig.driver) - datasource.setJdbcUrl(DatabaseConfig.url) - datasource.setUser(DatabaseConfig.user) - datasource.setPassword(DatabaseConfig.password) - - logger.debug("load database connection pool") - - SlickDatabase.forDataSource(datasource) - } - - def apply(): SlickDatabase = db - - def getSession(req: ServletRequest): Session = - req.getAttribute(Keys.Request.DBSession).asInstanceOf[Session] - -} diff --git a/src/main/scala/ssh/GitCommand.scala b/src/main/scala/ssh/GitCommand.scala deleted file mode 100644 index 35fb67b..0000000 --- a/src/main/scala/ssh/GitCommand.scala +++ /dev/null @@ -1,132 +0,0 @@ -package ssh - -import org.apache.sshd.server.{CommandFactory, Environment, ExitCallback, Command} -import org.slf4j.LoggerFactory -import java.io.{InputStream, OutputStream} -import util.ControlUtil._ -import org.eclipse.jgit.api.Git -import util.Directory._ -import org.eclipse.jgit.transport.{ReceivePack, UploadPack} -import org.apache.sshd.server.command.UnknownCommand -import servlet.{Database, CommitLogHook} -import service.{AccountService, RepositoryService, SystemSettingsService} -import org.eclipse.jgit.errors.RepositoryNotFoundException -import model.Session - -object GitCommand { - val CommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-_.]+).git'\Z""".r -} - -abstract class GitCommand(val owner: String, val repoName: String) extends Command { - self: RepositoryService with AccountService => - - private val logger = LoggerFactory.getLogger(classOf[GitCommand]) - protected var err: OutputStream = null - protected var in: InputStream = null - protected var out: OutputStream = null - protected var callback: ExitCallback = null - - protected def runTask(user: String)(implicit session: Session): Unit - - private def newTask(user: String): Runnable = new Runnable { - override def run(): Unit = { - Database() withSession { implicit session => - try { - runTask(user) - callback.onExit(0) - } catch { - case e: RepositoryNotFoundException => - logger.info(e.getMessage) - callback.onExit(1, "Repository Not Found") - case e: Throwable => - logger.error(e.getMessage, e) - callback.onExit(1) - } - } - } - } - - override def start(env: Environment): Unit = { - val user = env.getEnv.get("USER") - val thread = new Thread(newTask(user)) - thread.start() - } - - override def destroy(): Unit = {} - - override def setExitCallback(callback: ExitCallback): Unit = { - this.callback = callback - } - - override def setErrorStream(err: OutputStream): Unit = { - this.err = err - } - - override def setOutputStream(out: OutputStream): Unit = { - this.out = out - } - - override def setInputStream(in: InputStream): Unit = { - this.in = in - } - - protected def isWritableUser(username: String, repositoryInfo: RepositoryService.RepositoryInfo) - (implicit session: Session): Boolean = - getAccountByUserName(username) match { - case Some(account) => hasWritePermission(repositoryInfo.owner, repositoryInfo.name, Some(account)) - case None => false - } - -} - -class GitUploadPack(owner: String, repoName: String, baseUrl: String) extends GitCommand(owner, repoName) - with RepositoryService with AccountService { - - override protected def runTask(user: String)(implicit session: Session): Unit = { - getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""), baseUrl).foreach { repositoryInfo => - if(!repositoryInfo.repository.isPrivate || isWritableUser(user, repositoryInfo)){ - using(Git.open(getRepositoryDir(owner, repoName))) { git => - val repository = git.getRepository - val upload = new UploadPack(repository) - upload.upload(in, out, err) - } - } - } - } - -} - -class GitReceivePack(owner: String, repoName: String, baseUrl: String) extends GitCommand(owner, repoName) - with SystemSettingsService with RepositoryService with AccountService { - - override protected def runTask(user: String)(implicit session: Session): Unit = { - getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""), baseUrl).foreach { repositoryInfo => - if(isWritableUser(user, repositoryInfo)){ - using(Git.open(getRepositoryDir(owner, repoName))) { git => - val repository = git.getRepository - val receive = new ReceivePack(repository) - if(!repoName.endsWith(".wiki")){ - val hook = new CommitLogHook(owner, repoName, user, baseUrl) - receive.setPreReceiveHook(hook) - receive.setPostReceiveHook(hook) - } - receive.receive(in, out, err) - } - } - } - } - -} - -class GitCommandFactory(baseUrl: String) extends CommandFactory { - private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory]) - - override def createCommand(command: String): Command = { - logger.debug(s"command: $command") - command match { - case GitCommand.CommandRegex("upload", owner, repoName) => new GitUploadPack(owner, repoName, baseUrl) - case GitCommand.CommandRegex("receive", owner, repoName) => new GitReceivePack(owner, repoName, baseUrl) - case _ => new UnknownCommand(command) - } - } -} diff --git a/src/main/scala/ssh/NoShell.scala b/src/main/scala/ssh/NoShell.scala deleted file mode 100644 index c107be6..0000000 --- a/src/main/scala/ssh/NoShell.scala +++ /dev/null @@ -1,62 +0,0 @@ -package ssh - -import org.apache.sshd.common.Factory -import org.apache.sshd.server.{Environment, ExitCallback, Command} -import java.io.{OutputStream, InputStream} -import org.eclipse.jgit.lib.Constants -import service.SystemSettingsService - -class NoShell extends Factory[Command] with SystemSettingsService { - override def create(): Command = new Command() { - private var in: InputStream = null - private var out: OutputStream = null - private var err: OutputStream = null - private var callback: ExitCallback = null - - override def start(env: Environment): Unit = { - val user = env.getEnv.get("USER") - val port = loadSystemSettings().sshPort.getOrElse(SystemSettingsService.DefaultSshPort) - val message = - """ - | Welcome to - | _____ _ _ ____ _ _ - | / ____| (_) | | | _ \ | | | | - | | | __ _ | |_ | |_) | _ _ ___ | | __ ___ | |_ - | | | |_ | | | | __| | _ < | | | | / __| | |/ / / _ \ | __| - | | |__| | | | | |_ | |_) | | |_| | | (__ | < | __/ | |_ - | \_____| |_| \__| |____/ \__,_| \___| |_|\_\ \___| \__| - | - | Successfully SSH Access. - | But interactive shell is disabled. - | - | Please use: - | - | git clone ssh://%s@GITBUCKET_HOST:%d/OWNER/REPOSITORY_NAME.git - """.stripMargin.format(user, port).replace("\n", "\r\n") + "\r\n" - err.write(Constants.encode(message)) - err.flush() - in.close() - out.close() - err.close() - callback.onExit(127) - } - - override def destroy(): Unit = {} - - override def setInputStream(in: InputStream): Unit = { - this.in = in - } - - override def setOutputStream(out: OutputStream): Unit = { - this.out = out - } - - override def setErrorStream(err: OutputStream): Unit = { - this.err = err - } - - override def setExitCallback(callback: ExitCallback): Unit = { - this.callback = callback - } - } -} diff --git a/src/main/scala/ssh/PublicKeyAuthenticator.scala b/src/main/scala/ssh/PublicKeyAuthenticator.scala deleted file mode 100644 index ddcc724..0000000 --- a/src/main/scala/ssh/PublicKeyAuthenticator.scala +++ /dev/null @@ -1,22 +0,0 @@ -package ssh - -import org.apache.sshd.server.PublickeyAuthenticator -import org.apache.sshd.server.session.ServerSession -import java.security.PublicKey -import service.SshKeyService -import servlet.Database - -class PublicKeyAuthenticator extends PublickeyAuthenticator with SshKeyService { - - override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean = { - Database() withSession { implicit session => - getPublicKeys(username).exists { sshKey => - SshUtil.str2PublicKey(sshKey.publicKey) match { - case Some(publicKey) => key.equals(publicKey) - case _ => false - } - } - } - } - -} diff --git a/src/main/scala/ssh/SshServerListener.scala b/src/main/scala/ssh/SshServerListener.scala deleted file mode 100644 index b441cae..0000000 --- a/src/main/scala/ssh/SshServerListener.scala +++ /dev/null @@ -1,69 +0,0 @@ -package ssh - -import javax.servlet.{ServletContextEvent, ServletContextListener} -import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider -import org.slf4j.LoggerFactory -import util.Directory -import service.SystemSettingsService -import java.util.concurrent.atomic.AtomicBoolean - -object SshServer { - private val logger = LoggerFactory.getLogger(SshServer.getClass) - private val server = org.apache.sshd.SshServer.setUpDefaultServer() - private val active = new AtomicBoolean(false) - - private def configure(port: Int, baseUrl: String) = { - server.setPort(port) - server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(s"${Directory.GitBucketHome}/gitbucket.ser")) - server.setPublickeyAuthenticator(new PublicKeyAuthenticator) - server.setCommandFactory(new GitCommandFactory(baseUrl)) - server.setShellFactory(new NoShell) - } - - def start(port: Int, baseUrl: String) = { - if(active.compareAndSet(false, true)){ - configure(port, baseUrl) - server.start() - logger.info(s"Start SSH Server Listen on ${server.getPort}") - } - } - - def stop() = { - if(active.compareAndSet(true, false)){ - server.stop(true) - logger.info("SSH Server is stopped.") - } - } - - def isActive = active.get -} - -/* - * Start a SSH Server Daemon - * - * How to use: - * git clone ssh://username@host_or_ip:29418/owner/repository_name.git - */ -class SshServerListener extends ServletContextListener with SystemSettingsService { - - private val logger = LoggerFactory.getLogger(classOf[SshServerListener]) - - override def contextInitialized(sce: ServletContextEvent): Unit = { - val settings = loadSystemSettings() - if(settings.ssh){ - settings.baseUrl match { - case None => - logger.error("Could not start SshServer because the baseUrl is not configured.") - case Some(baseUrl) => - SshServer.start(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), baseUrl) - } - } - } - - override def contextDestroyed(sce: ServletContextEvent): Unit = { - if(loadSystemSettings().ssh){ - SshServer.stop() - } - } - -} diff --git a/src/main/scala/ssh/SshUtil.scala b/src/main/scala/ssh/SshUtil.scala deleted file mode 100644 index 9d6484c..0000000 --- a/src/main/scala/ssh/SshUtil.scala +++ /dev/null @@ -1,36 +0,0 @@ -package ssh - -import java.security.PublicKey -import org.slf4j.LoggerFactory -import org.apache.commons.codec.binary.Base64 -import org.eclipse.jgit.lib.Constants -import org.apache.sshd.common.util.{KeyUtils, Buffer} - -object SshUtil { - - private val logger = LoggerFactory.getLogger(SshUtil.getClass) - - def str2PublicKey(key: String): Option[PublicKey] = { - // TODO RFC 4716 Public Key is not supported... - val parts = key.split(" ") - if (parts.size < 2) { - logger.debug(s"Invalid PublicKey Format: ${key}") - return None - } - try { - val encodedKey = parts(1) - val decode = Base64.decodeBase64(Constants.encodeASCII(encodedKey)) - Some(new Buffer(decode).getRawPublicKey) - } catch { - case e: Throwable => - logger.debug(e.getMessage, e) - None - } - } - - def fingerPrint(key: String): Option[String] = str2PublicKey(key) match { - case Some(publicKey) => Some(KeyUtils.getFingerPrint(publicKey)) - case None => None - } - -} diff --git a/src/main/scala/util/Authenticator.scala b/src/main/scala/util/Authenticator.scala deleted file mode 100644 index f40af7b..0000000 --- a/src/main/scala/util/Authenticator.scala +++ /dev/null @@ -1,181 +0,0 @@ -package util - -import app.ControllerBase -import service._ -import RepositoryService.RepositoryInfo -import util.Implicits._ -import util.ControlUtil._ - -/** - * Allows only oneself and administrators. - */ -trait OneselfAuthenticator { self: ControllerBase => - protected def oneselfOnly(action: => Any) = { authenticate(action) } - protected def oneselfOnly[T](action: T => Any) = (form: T) => { authenticate(action(form)) } - - private def authenticate(action: => Any) = { - { - defining(request.paths){ paths => - context.loginAccount match { - case Some(x) if(x.isAdmin) => action - case Some(x) if(paths(0) == x.userName) => action - case _ => Unauthorized() - } - } - } - } -} - -/** - * Allows only the repository owner and administrators. - */ -trait OwnerAuthenticator { self: ControllerBase with RepositoryService with AccountService => - protected def ownerOnly(action: (RepositoryInfo) => Any) = { authenticate(action) } - protected def ownerOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) } - - private def authenticate(action: (RepositoryInfo) => Any) = { - { - defining(request.paths){ paths => - getRepository(paths(0), paths(1), baseUrl).map { repository => - context.loginAccount match { - case Some(x) if(x.isAdmin) => action(repository) - case Some(x) if(repository.owner == x.userName) => action(repository) - case Some(x) if(getGroupMembers(repository.owner).exists { member => - member.userName == x.userName && member.isManager == true - }) => action(repository) - case _ => Unauthorized() - } - } getOrElse NotFound() - } - } - } -} - -/** - * Allows only signed in users. - */ -trait UsersAuthenticator { self: ControllerBase => - protected def usersOnly(action: => Any) = { authenticate(action) } - protected def usersOnly[T](action: T => Any) = (form: T) => { authenticate(action(form)) } - - private def authenticate(action: => Any) = { - { - context.loginAccount match { - case Some(x) => action - case None => Unauthorized() - } - } - } -} - -/** - * Allows only administrators. - */ -trait AdminAuthenticator { self: ControllerBase => - protected def adminOnly(action: => Any) = { authenticate(action) } - protected def adminOnly[T](action: T => Any) = (form: T) => { authenticate(action(form)) } - - private def authenticate(action: => Any) = { - { - context.loginAccount match { - case Some(x) if(x.isAdmin) => action - case _ => Unauthorized() - } - } - } -} - -/** - * Allows only collaborators and administrators. - */ -trait CollaboratorsAuthenticator { self: ControllerBase with RepositoryService => - protected def collaboratorsOnly(action: (RepositoryInfo) => Any) = { authenticate(action) } - protected def collaboratorsOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) } - - private def authenticate(action: (RepositoryInfo) => Any) = { - { - defining(request.paths){ paths => - getRepository(paths(0), paths(1), baseUrl).map { repository => - context.loginAccount match { - case Some(x) if(x.isAdmin) => action(repository) - case Some(x) if(paths(0) == x.userName) => action(repository) - case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository) - case _ => Unauthorized() - } - } getOrElse NotFound() - } - } - } -} - -/** - * Allows only the repository owner (or manager for group repository) and administrators. - */ -trait ReferrerAuthenticator { self: ControllerBase with RepositoryService => - protected def referrersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) } - protected def referrersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) } - - private def authenticate(action: (RepositoryInfo) => Any) = { - { - defining(request.paths){ paths => - getRepository(paths(0), paths(1), baseUrl).map { repository => - if(!repository.repository.isPrivate){ - action(repository) - } else { - context.loginAccount match { - case Some(x) if(x.isAdmin) => action(repository) - case Some(x) if(paths(0) == x.userName) => action(repository) - case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository) - case _ => Unauthorized() - } - } - } getOrElse NotFound() - } - } - } -} - -/** - * Allows only signed in users which can access the repository. - */ -trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService => - protected def readableUsersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) } - protected def readableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) } - - private def authenticate(action: (RepositoryInfo) => Any) = { - { - defining(request.paths){ paths => - getRepository(paths(0), paths(1), baseUrl).map { repository => - context.loginAccount match { - case Some(x) if(x.isAdmin) => action(repository) - case Some(x) if(!repository.repository.isPrivate) => action(repository) - case Some(x) if(paths(0) == x.userName) => action(repository) - case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository) - case _ => Unauthorized() - } - } getOrElse NotFound() - } - } - } -} - -/** - * Allows only the group managers. - */ -trait GroupManagerAuthenticator { self: ControllerBase with AccountService => - protected def managersOnly(action: => Any) = { authenticate(action) } - protected def managersOnly[T](action: T => Any) = (form: T) => { authenticate(action(form)) } - - private def authenticate(action: => Any) = { - { - defining(request.paths){ paths => - context.loginAccount match { - case Some(x) if(getGroupMembers(paths(0)).exists { member => - member.userName == x.userName && member.isManager - }) => action - case _ => Unauthorized() - } - } - } - } -} diff --git a/src/main/scala/util/ControlUtil.scala b/src/main/scala/util/ControlUtil.scala deleted file mode 100644 index 7945f32..0000000 --- a/src/main/scala/util/ControlUtil.scala +++ /dev/null @@ -1,46 +0,0 @@ -package util - -import org.eclipse.jgit.api.Git -import org.eclipse.jgit.revwalk.RevWalk -import org.eclipse.jgit.treewalk.TreeWalk -import scala.util.control.Exception._ -import scala.language.reflectiveCalls - -/** - * Provides control facilities. - */ -object ControlUtil { - - def defining[A, B](value: A)(f: A => B): B = f(value) - - def using[A <% { def close(): Unit }, B](resource: A)(f: A => B): B = - try f(resource) finally { - if(resource != null){ - ignoring(classOf[Throwable]) { - resource.close() - } - } - } - - def using[T](git: Git)(f: Git => T): T = - try f(git) finally git.getRepository.close() - - def using[T](git1: Git, git2: Git)(f: (Git, Git) => T): T = - try f(git1, git2) finally { - git1.getRepository.close() - git2.getRepository.close() - } - - def using[T](revWalk: RevWalk)(f: RevWalk => T): T = - try f(revWalk) finally revWalk.release() - - def using[T](treeWalk: TreeWalk)(f: TreeWalk => T): T = - try f(treeWalk) finally treeWalk.release() - - def ignore[T](f: => Unit): Unit = try { - f - } catch { - case e: Exception => () - } - -} diff --git a/src/main/scala/util/DatabaseConfig.scala b/src/main/scala/util/DatabaseConfig.scala deleted file mode 100644 index 86453e3..0000000 --- a/src/main/scala/util/DatabaseConfig.scala +++ /dev/null @@ -1,19 +0,0 @@ -package util - -import com.typesafe.config.ConfigFactory -import util.Directory.DatabaseHome - -object DatabaseConfig { - - private val config = ConfigFactory.load("database") - private val dbUrl = config.getString("db.url") - - def url(directory: Option[String]): String = - dbUrl.replace("${DatabaseHome}", directory.getOrElse(DatabaseHome)) - - val url: String = url(None) - val user: String = config.getString("db.user") - val password: String = config.getString("db.password") - val driver: String = config.getString("db.driver") - -} diff --git a/src/main/scala/util/Directory.scala b/src/main/scala/util/Directory.scala deleted file mode 100644 index 9008171..0000000 --- a/src/main/scala/util/Directory.scala +++ /dev/null @@ -1,87 +0,0 @@ -package util - -import java.io.File -import util.ControlUtil._ -import org.apache.commons.io.FileUtils - -/** - * Provides directories used by GitBucket. - */ -object Directory { - - val GitBucketHome = (System.getProperty("gitbucket.home") match { - // -Dgitbucket.home=
") - var text: String = node.getText - while (text.charAt(0) == '\n') { - printer.print("") - } -} - -class GitBucketHtmlSerializer( - markdown: String, - repository: service.RepositoryService.RepositoryInfo, - enableWikiLink: Boolean, - enableRefsLink: Boolean, - enableTaskList: Boolean, - hasWritePermission: Boolean, - pages: List[String] - )(implicit val context: app.Context) extends ToHtmlSerializer( - new GitBucketLinkRender(context, repository, enableWikiLink, pages), - Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava - ) with LinkConverter with RequestCache { - - override protected def printImageTag(imageNode: SuperNode, url: String): Unit = { - printer.print("") - .print("
") - text = text.substring(1) - } - printer.printEncoded(text) - printer.print("
${value.body.trim.split("\n").map(_.trim).mkString("\n")}") - - /** - * Implicit conversion to add mkHtml() to Seq[Html]. - */ - implicit class RichHtmlSeq(seq: Seq[Html]) { - def mkHtml(separator: String) = Html(seq.mkString(separator)) - def mkHtml(separator: scala.xml.Elem) = Html(seq.mkString(separator.toString)) - } - - def commitStateIcon(state: CommitState) = Html(state match { - case CommitState.PENDING => "●" - case CommitState.SUCCESS => "✔" - case CommitState.ERROR => "×" - case CommitState.FAILURE => "×" - }) - - def commitStateText(state: CommitState, commitId:String) = state match { - case CommitState.PENDING => "Waiting to hear about "+commitId.substring(0,8) - case CommitState.SUCCESS => "All is well" - case CommitState.ERROR => "Failed" - case CommitState.FAILURE => "Failed" - } -} diff --git a/src/main/twirl/account/activity.scala.html b/src/main/twirl/account/activity.scala.html deleted file mode 100644 index ca4c3c7..0000000 --- a/src/main/twirl/account/activity.scala.html +++ /dev/null @@ -1,9 +0,0 @@ -@(account: model.Account, groupNames: List[String], activities: List[model.Activity])(implicit context: app.Context) -@import context._ -@import view.helpers._ -@main(account, groupNames, "activity"){ - - @helper.html.activities(activities) -} diff --git a/src/main/twirl/account/application.scala.html b/src/main/twirl/account/application.scala.html deleted file mode 100644 index e6a5d06..0000000 --- a/src/main/twirl/account/application.scala.html +++ /dev/null @@ -1,55 +0,0 @@ -@(account: model.Account, personalTokens: List[model.AccessToken], gneratedToken:Option[(model.AccessToken, String)])(implicit context: app.Context) -@import context._ -@import view.helpers._ -@html.main("Applications"){ -
-
-
- @avatar(account.userName, 20)
- @account.userName
- @if(account.isGroupAccount){
- (Group)
- } else {
- @if(account.isAdmin){
- (Administrator)
- } else {
- (Normal)
- }
- }
- @if(account.isGroupAccount){
- @members(account.userName).map { userName =>
- @avatar(userName, 20, tooltip = true)
- }
- }
-
-
-
- - @if(!account.isGroupAccount){ - @account.mailAddress - } - @account.url.map { url => - @url - } -
- Registered: @datetime(account.registeredDate)
- Updated: @datetime(account.updatedDate)
- @if(!account.isGroupAccount){
- Last Login: @account.lastLoginDate.map(datetime)
- }
-
- |
-
- @dashboard.html.header(openCount, closedCount, condition, groups) - | -
---|
- @if(issue.isPullRequest){
- ![]() ![]()
- #@issue.issueId opened by @user(issue.openedUserName, styleClass="username") @datetime(issue.registeredDate)
- @milestone.map { milestone =>
-
- ![]() |
-
+
+
+ @avatar(account.userName, 20)
+ @account.userName
+ @if(account.isGroupAccount){
+ (Group)
+ } else {
+ @if(account.isAdmin){
+ (Administrator)
+ } else {
+ (Normal)
+ }
+ }
+ @if(account.isGroupAccount){
+ @members(account.userName).map { userName =>
+ @avatar(userName, 20, tooltip = true)
+ }
+ }
+
+
+
+ + @if(!account.isGroupAccount){ + @account.mailAddress + } + @account.url.map { url => + @url + } +
+ Registered: @datetime(account.registeredDate)
+ Updated: @datetime(account.updatedDate)
+ @if(!account.isGroupAccount){
+ Last Login: @account.lastLoginDate.map(datetime)
+ }
+
+ |
+
+ @dashboard.html.header(openCount, closedCount, condition, groups) + | +
---|
+ @if(issue.isPullRequest){
+ ![]() ![]()
+ #@issue.issueId opened by @user(issue.openedUserName, styleClass="username") @datetime(issue.registeredDate)
+ @milestone.map { milestone =>
+
+ ![]() |
+
+ @if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
+ ![]() ![]() ![]() ![]() |
+
---|
+ @if(diff.newContent != None || diff.oldContent != None){ + + + + } else { + Not supported + } + | +
+
+ New repository
+
+ Your repositories (@userRepositories.size)
+ |
+
---|
No repositories | +
+ @helper.html.repositoryicon(repository, false) + @if(repository.owner == loginAccount.get.userName){ + @repository.name + } else { + @repository.owner/@repository.name + } + | +
+ Recent updated repositories + | +
---|
No repositories | +
+ @helper.html.repositoryicon(repository, false) + @repository.owner/@repository.name + | +
@pullreq.map(_.commitIdTo.substring(0, 7))
into
+ @if(pullreq.get.requestUserName == repository.owner){
+ @pullreq.map(_.branch) from @pullreq.map(_.requestBranch)
+ } else {
+ @pullreq.map(_.userName):@pullreq.map(_.branch) from @pullreq.map(_.requestUserName):@pullreq.map(_.requestBranch)
+ }
+ @helper.html.datetimeago(comment.registeredDate)
+ + @labels.size labels + | +
---|
+ No labels to show. + @if(hasWritePermission){ + Create a new label. + } + | +
+
+
+
+
+ @helper.html.dropdown("Author", flat = true) {
+ @collaborators.map { collaborator =>
+
+ @if(hasWritePermission){
+
+ @helper.html.dropdown("Mark as", flat = true) {
+
+ }
+ |
+
---|
+ @if(target == "issues"){ + No issues to show. + } else { + No pull requests to show. + } + @if(condition.labels.nonEmpty || condition.milestone.isDefined){ + Clear active filters. + } else { + @if(repository.isDefined){ + @if(target == "issues"){ + Create a new issue. + } else { + Create a new pull request. + } + } + } + | +
+ @if(hasWritePermission){
+
+ }
+ ![]() ![]()
+ #@issue.issueId opened @helper.html.datetimeago(issue.registeredDate) by @user(issue.openedUserName, styleClass="username")
+ @milestone.map { milestone =>
+
+ ![]() |
+
+
+
+ |
+
---|
+
+
+ @if(milestone.description.isDefined){
+
+ @milestone.title
+
+
+ @if(milestone.closedDate.isDefined){
+ Closed @helper.html.datetimeago(milestone.closedDate.get)
+ } else {
+ @milestone.dueDate.map { dueDate =>
+ @if(isPast(dueDate)){
+
+ ![]()
+ @progress(openCount + closedCount, closedCount)
+
+
+
+
+ @if(closedCount == 0){
+ 0%
+ } else {
+ @((closedCount.toDouble / (openCount + closedCount).toDouble * 100).toInt)%
+ } complete
+ @openCount open
+ @closedCount closed
+
+
+
+ @markdown(milestone.description.get, repository, false, false)
+
+ }
+ |
+
+ No milestones to show. + @if(hasWritePermission){ + Create a new milestone. + } + | +
@description
+ } +
+
+ ![]() |
+
+
+ ![]() |
+
+
+ ![]() |
+
@date(day.head.commitTime) | +|||
---|---|---|---|
+ @avatar(commit, 20) + @user(commit.authorName, commit.authorEmailAddress, "username") + | +@commit.shortMessage | ++ @if(comments.isDefined){ + @comments.get.flatMap @{ + case comment: CommitComment => Some(comment) + case other => None + }.count(t => t.commitId == commit.id && !t.pullRequest) + } + | ++ @commit.id.substring(0, 7) + | +
+ There isn't anything to compare.+ @originRepository.owner:@originId and @forkedRepository.owner:@forkedId are identical. + |
+
Showing you all comments on commits in this comparison.
+ @issues.html.commentlist(None, comments, hasWritePermission, repository, None) + } + } +} + diff --git a/src/main/twirl/gitbucket/core/pulls/conversation.scala.html b/src/main/twirl/gitbucket/core/pulls/conversation.scala.html new file mode 100644 index 0000000..d005b40 --- /dev/null +++ b/src/main/twirl/gitbucket/core/pulls/conversation.scala.html @@ -0,0 +1,68 @@ +@(issue: gitbucket.core.model.Issue, + pullreq: gitbucket.core.model.PullRequest, + comments: List[gitbucket.core.model.Comment], + issueLabels: List[gitbucket.core.model.Label], + collaborators: List[String], + milestones: List[(gitbucket.core.model.Milestone, Int, Int)], + labels: List[gitbucket.core.model.Label], + hasWritePermission: Boolean, + repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context) +@import context._ +@import gitbucket.core.view.helpers._ +@import gitbucket.core.model._ + +Don't worry, you can still submit the pull request.
+} else { +These branches can be automatically merged.
+} + diff --git a/src/main/twirl/gitbucket/core/pulls/mergeguide.scala.html b/src/main/twirl/gitbucket/core/pulls/mergeguide.scala.html new file mode 100644 index 0000000..f6c1d4a --- /dev/null +++ b/src/main/twirl/gitbucket/core/pulls/mergeguide.scala.html @@ -0,0 +1,153 @@ +@(hasConflict: Boolean, + hasProblem: Boolean, + issue: gitbucket.core.model.Issue, + pullreq: gitbucket.core.model.PullRequest, + statuses: List[model.CommitStatus], + repository: gitbucket.core.service.RepositoryService.RepositoryInfo, + requestRepositoryUrl: String)(implicit context: gitbucket.core.controller.Context) +@import context._ +@import gitbucket.core.view.helpers._ +@import model.CommitState +@pullreq.userName:@pullreq.branch
from @pullreq.requestUserName:@pullreq.requestBranch
+ @helper.html.datetimeago(comment.registeredDate)
+
+ }.getOrElse {
+ Closed
+
+ @user(issue.openedUserName, styleClass="username strong") wants to merge @commits.size @plural(commits.size, "commit")
+ into @pullreq.userName:@pullreq.branch
from @pullreq.requestUserName:@pullreq.requestBranch
+
+ }
+ } else {
+ Open
+
+ @user(issue.openedUserName, styleClass="username strong") wants to merge @commits.size @plural(commits.size, "commit")
+ into @pullreq.userName:@pullreq.branch
from @pullreq.requestUserName:@pullreq.requestBranch
+
+ }
+
+
+ @avatar(latestCommit, 20)
+ @user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")
+ @helper.html.datetimeago(latestCommit.commitTime)
+
+
+
+ |
+
---|
+ @if(content.viewType == "text"){
+ @defining(pathList.reverse.head) { file =>
+ @if(renderableSuffixes.find(suffix => file.toLowerCase.endsWith(suffix))) {
+
+ @renderMarkup(pathList, content.content.get, branch, repository, false, false)
+
+ } else {
+ @content.content.get+ } + } + } + @if(content.viewType == "image"){ + |
+
All branches | +
---|
+
+ @branch.mergeInfo.map{ info =>
+ @prs.map{ case (pull, issue) =>
+ #@issue.issueId
+ @if(issue.closed) {
+ @if(info.isMerged){
+ Merged
+ }else{
+ Closed
+ }
+ } else {
+ Open
+ }
+ }.getOrElse{
+ @if(context.loginAccount.isDefined){
+ New Pull Request
+ }else{
+ Compare
+ }
+ }
+ @if(hasWritePermission){
+ @if(prs.map(!_._2.closed).getOrElse(false)){
+
+ }else{
+
+ }
+ }
+ }
+
+
+ @branch.name
+
+
+
+ @if(branch.mergeInfo.isEmpty){
+ Default
+ }else{
+ @branch.mergeInfo.map{ info =>
+
+ }
+ }
+
+
+
+
+ |
+
+ Browse code
+
+ @link(commit.summary, repository)
+ @if(commit.description.isDefined){
+ @link(commit.description.get, repository)+ } + + |
+
---|
+
+
+
+
+
+ @if(commit.parents.size == 0){
+ 0 parent
+ }
+ @if(commit.parents.size == 1){
+ 1 parent
+ @commit.parents(0).substring(0, 7)
+ }
+ commit @commit.id
+
+ @if(commit.parents.size > 1){
+
+ @commit.parents.size parents
+ @commit.parents.map { parent =>
+ @parent.substring(0, 7)
+ }.mkHtml(" + ")
+
+
+ }
+ |
+
@date(day.head.commitTime) | +
---|
+
+
+
+ @avatar(commit, 40)
+
+ |
+
+ ... + + } + | + + @if(latestCommit.description.isDefined){ +|||
---|---|---|---|
+ + | +|||
+ | .. | ++ | + |
+ @if(file.isDirectory){
+ @if(file.linkUrl.isDefined){
+ ![]() ![]() ![]() |
+ + @if(file.isDirectory){ + @if(file.linkUrl.isDefined){ + + @file.name.split("/").toList.init match { + case Nil => {} + case list => {@list.mkString("", "/", "/")} + }@file.name.split("/").toList.last + + } else { + + @file.name.split("/").toList.init match { + case Nil => {} + case list => {@list.mkString("", "/", "/")} + }@file.name.split("/").toList.last + + } + } else { + @file.name + } + | ++ | + + [@user(file.author, file.mailAddress)] +@helper.html.datetimeago(file.time, false) | +
Tag | +Date | +Commit | +Download | +
---|---|---|---|
@tag.name | +@helper.html.datetimeago(tag.time, false) | +@tag.id.substring(0, 10) | ++ ZIP + TAR.GZ + | +
@Html(file.highlightText)+
@Html(issue.highlightText)+ } +
+ @if(systemSettings.allowAccountRegistration){ + + } + Sign in + | +
---|
+ + | +
+ Revisions
+
+
+
+ |
+ ||
---|---|---|
+ | @avatar(commit, 20) @user(commit.authorName, commit.authorEmailAddress) | ++ @helper.html.datetimeago(commit.authorTime): @commit.shortMessage + | +
Pages @pages.length | +
---|
+
|
+
- @if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
- ![]() ![]() ![]() ![]() |
-
---|
- @if(diff.newContent != None || diff.oldContent != None){ - - - - } else { - Not supported - } - | -
-
- New repository
-
- Your repositories (@userRepositories.size)
- |
-
---|
No repositories | -
- @helper.html.repositoryicon(repository, false) - @if(repository.owner == loginAccount.get.userName){ - @repository.name - } else { - @repository.owner/@repository.name - } - | -
- Recent updated repositories - | -
---|
No repositories | -
- @helper.html.repositoryicon(repository, false) - @repository.owner/@repository.name - | -
@pullreq.map(_.commitIdTo.substring(0, 7))
into
- @if(pullreq.get.requestUserName == repository.owner){
- @pullreq.map(_.branch) from @pullreq.map(_.requestBranch)
- } else {
- @pullreq.map(_.userName):@pullreq.map(_.branch) from @pullreq.map(_.requestUserName):@pullreq.map(_.requestBranch)
- }
- @helper.html.datetimeago(comment.registeredDate)
- - @labels.size labels - | -
---|
- No labels to show. - @if(hasWritePermission){ - Create a new label. - } - | -
-
-
-
-
- @helper.html.dropdown("Author", flat = true) {
- @collaborators.map { collaborator =>
-
- @if(hasWritePermission){
-
- @helper.html.dropdown("Mark as", flat = true) {
-
- }
- |
-
---|
- @if(target == "issues"){ - No issues to show. - } else { - No pull requests to show. - } - @if(condition.labels.nonEmpty || condition.milestone.isDefined){ - Clear active filters. - } else { - @if(repository.isDefined){ - @if(target == "issues"){ - Create a new issue. - } else { - Create a new pull request. - } - } - } - | -
- @if(hasWritePermission){
-
- }
- ![]() ![]()
- #@issue.issueId opened @helper.html.datetimeago(issue.registeredDate) by @user(issue.openedUserName, styleClass="username")
- @milestone.map { milestone =>
-
- ![]() |
-
-
-
- |
-
---|
-
-
- @if(milestone.description.isDefined){
-
- @milestone.title
-
-
- @if(milestone.closedDate.isDefined){
- Closed @helper.html.datetimeago(milestone.closedDate.get)
- } else {
- @milestone.dueDate.map { dueDate =>
- @if(isPast(dueDate)){
-
- ![]()
- @progress(openCount + closedCount, closedCount)
-
-
-
-
- @if(closedCount == 0){
- 0%
- } else {
- @((closedCount.toDouble / (openCount + closedCount).toDouble * 100).toInt)%
- } complete
- @openCount open
- @closedCount closed
-
-
-
- @markdown(milestone.description.get, repository, false, false)
-
- }
- |
-
- No milestones to show. - @if(hasWritePermission){ - Create a new milestone. - } - | -
@description
- } -
-
- ![]() |
-
-
- ![]() |
-
-
- ![]() |
-
@date(day.head.commitTime) | -|||
---|---|---|---|
- @avatar(commit, 20) - @user(commit.authorName, commit.authorEmailAddress, "username") - | -@commit.shortMessage | -- @if(comments.isDefined){ - @comments.get.flatMap @{ - case comment: model.CommitComment => Some(comment) - case other => None - }.count(t => t.commitId == commit.id && !t.pullRequest) - } - | -- @commit.id.substring(0, 7) - | -
- There isn't anything to compare.- @originRepository.owner:@originId and @forkedRepository.owner:@forkedId are identical. - |
-
Showing you all comments on commits in this comparison.
- @issues.html.commentlist(None, comments, hasWritePermission, repository, None) - } - } -} - diff --git a/src/main/twirl/pulls/conversation.scala.html b/src/main/twirl/pulls/conversation.scala.html deleted file mode 100644 index 82a8982..0000000 --- a/src/main/twirl/pulls/conversation.scala.html +++ /dev/null @@ -1,67 +0,0 @@ -@(issue: model.Issue, - pullreq: model.PullRequest, - comments: List[model.Comment], - issueLabels: List[model.Label], - collaborators: List[String], - milestones: List[(model.Milestone, Int, Int)], - labels: List[model.Label], - hasWritePermission: Boolean, - repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context) -@import context._ -@import model.{IssueComment, CommitState} -@import view.helpers._ -Don't worry, you can still submit the pull request.
-} else { -These branches can be automatically merged.
-} - diff --git a/src/main/twirl/pulls/mergeguide.scala.html b/src/main/twirl/pulls/mergeguide.scala.html deleted file mode 100644 index 21733ad..0000000 --- a/src/main/twirl/pulls/mergeguide.scala.html +++ /dev/null @@ -1,154 +0,0 @@ -@(hasConflict: Boolean, - hasProblem: Boolean, - issue: model.Issue, - pullreq: model.PullRequest, - statuses: List[model.CommitStatus], - repository: service.RepositoryService.RepositoryInfo, - requestRepositoryUrl: String)(implicit context: app.Context) -@import context._ -@import view.helpers._ -@import model.CommitState -@pullreq.userName:@pullreq.branch
from @pullreq.requestUserName:@pullreq.requestBranch
- @helper.html.datetimeago(comment.registeredDate)
-
- }.getOrElse {
- Closed
-
- @user(issue.openedUserName, styleClass="username strong") wants to merge @commits.size @plural(commits.size, "commit")
- into @pullreq.userName:@pullreq.branch
from @pullreq.requestUserName:@pullreq.requestBranch
-
- }
- } else {
- Open
-
- @user(issue.openedUserName, styleClass="username strong") wants to merge @commits.size @plural(commits.size, "commit")
- into @pullreq.userName:@pullreq.branch
from @pullreq.requestUserName:@pullreq.requestBranch
-
- }
-
-
- @avatar(latestCommit, 20)
- @user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")
- @helper.html.datetimeago(latestCommit.commitTime)
-
-
-
- |
-
---|
- @if(content.viewType == "text"){
- @defining(pathList.reverse.head) { file =>
- @if(renderableSuffixes.find(suffix => file.toLowerCase.endsWith(suffix))) {
-
- @renderMarkup(pathList, content.content.get, branch, repository, false, false)
-
- } else {
- @content.content.get- } - } - } - @if(content.viewType == "image"){ - |
-
All branches | -
---|
-
- @branch.mergeInfo.map{ info =>
- @prs.map{ case (pull, issue) =>
- #@issue.issueId
- @if(issue.closed) {
- @if(info.isMerged){
- Merged
- }else{
- Closed
- }
- } else {
- Open
- }
- }.getOrElse{
- @if(context.loginAccount.isDefined){
- New Pull Request
- }else{
- Compare
- }
- }
- @if(hasWritePermission){
- @if(prs.map(!_._2.closed).getOrElse(false)){
-
- }else{
-
- }
- }
- }
-
-
- @branch.name
-
-
-
- @if(branch.mergeInfo.isEmpty){
- Default
- }else{
- @branch.mergeInfo.map{ info =>
-
- }
- }
-
-
-
-
- |
-
- Browse code
-
- @link(commit.summary, repository)
- @if(commit.description.isDefined){
- @link(commit.description.get, repository)- } - - |
-
---|
-
-
-
-
-
- @if(commit.parents.size == 0){
- 0 parent
- }
- @if(commit.parents.size == 1){
- 1 parent
- @commit.parents(0).substring(0, 7)
- }
- commit @commit.id
-
- @if(commit.parents.size > 1){
-
- @commit.parents.size parents
- @commit.parents.map { parent =>
- @parent.substring(0, 7)
- }.mkHtml(" + ")
-
-
- }
- |
-
@date(day.head.commitTime) | -
---|
-
-
-
- @avatar(commit, 40)
-
- |
-
- ... - - } - | - - @if(latestCommit.description.isDefined){ -|||
---|---|---|---|
- - | -|||
- | .. | -- | - |
- @if(file.isDirectory){
- @if(file.linkUrl.isDefined){
- ![]() ![]() ![]() |
- - @if(file.isDirectory){ - @if(file.linkUrl.isDefined){ - - @file.name.split("/").toList.init match { - case Nil => {} - case list => {@list.mkString("", "/", "/")} - }@file.name.split("/").toList.last - - } else { - - @file.name.split("/").toList.init match { - case Nil => {} - case list => {@list.mkString("", "/", "/")} - }@file.name.split("/").toList.last - - } - } else { - @file.name - } - | -- | - - [@user(file.author, file.mailAddress)] -@helper.html.datetimeago(file.time, false) | -
Tag | -Date | -Commit | -Download | -
---|---|---|---|
@tag.name | -@helper.html.datetimeago(tag.time, false) | -@tag.id.substring(0, 10) | -- ZIP - TAR.GZ - | -
@Html(file.highlightText)-
@Html(issue.highlightText)- } -
- @if(systemSettings.allowAccountRegistration){ - - } - Sign in - | -
---|
- - | -
- Revisions
-
-
-
- |
- ||
---|---|---|
- | @avatar(commit, 20) @user(commit.authorName, commit.authorEmailAddress) | -- @helper.html.datetimeago(commit.authorTime): @commit.shortMessage - | -
Pages @pages.length | -
---|
-
|
-