Compare commits

...

36 Commits
3.12 ... quill

Author SHA1 Message Date
Naoki Takezoe
8a9588f17f Moving to Quill 2016-03-10 18:30:48 +09:00
Naoki Takezoe
682f3a4c10 Moving to Quill 2016-03-10 16:40:25 +09:00
Naoki Takezoe
dad29d93c2 Moving to Quill 2016-03-10 13:27:02 +09:00
Naoki Takezoe
fc99de8a65 Merge branch 'master' into quill
# Conflicts:
#	build.sbt
#	src/main/scala/gitbucket/core/controller/AccountController.scala
#	src/main/scala/gitbucket/core/controller/UserManagementController.scala
2016-03-10 01:51:35 +09:00
Naoki Takezoe
ea7c8e62de Bump markedj to 1.0.7 2016-03-10 01:40:49 +09:00
Naoki Takezoe
b84421723b Merge pull request #1143 from mcs07/master
Fix special character encoding for blob links
2016-03-10 01:38:24 +09:00
Naoki Takezoe
9a42b93d1f Merge pull request #1140 from gitbucket/separate_api_controller
Separate API controller to improve routing performance
2016-03-10 01:34:56 +09:00
Matt Swain
e162cd956a Fix special character encoding for blob links 2016-03-08 12:37:58 +00:00
Naoki Takezoe
bc4af8e7c1 Finished to move AccountService to quill 2016-03-07 10:05:51 +09:00
Naoki Takezoe
cb3a79c9b3 Move some methods of AccountService to quill 2016-03-06 14:20:32 +09:00
Naoki Takezoe
b775ce157f Fix GroupMember model 2016-03-06 14:04:27 +09:00
Naoki Takezoe
70e2079c7f Update release.md 2016-03-06 12:58:44 +09:00
Naoki Takezoe
e8737d263a Merge pull request #1139 from ritschwumm/wip/sha256
generate sha-256 checksum
2016-03-06 12:57:29 +09:00
Naoki Takezoe
4c4b08f1b8 Merge pull request #1127 from McFoggy/issue-1117
add X-Hub-Signature security to wekhooks
2016-03-06 12:51:54 +09:00
Matthieu Brouillard
c7e1edf262 replace null by None 2016-03-05 21:12:51 +01:00
Naoki Takezoe
876bb396fd Fix testcase 2016-03-06 03:07:33 +09:00
Naoki Takezoe
a6788f858f Remove ControllerBase dependency from HandleCommentService 2016-03-06 03:04:45 +09:00
Naoki Takezoe
b263764730 Move createIssueComment() to IssuesService 2016-03-06 00:20:35 +09:00
Naoki Takezoe
1b1bd371a4 Fix testcase 2016-03-05 20:11:04 +09:00
Naoki Takezoe
f194a08cfe Separate API controller to improve routing performance 2016-03-05 19:47:27 +09:00
Naoki Takezoe
1211bfc7be Merge UserManagementController to SystemSettingsController to reduce filter mapping 2016-03-05 11:31:59 +09:00
Naoki Takezoe
eab7011e0f Merge remote-tracking branch 'origin/master' 2016-03-05 10:52:43 +09:00
Naoki Takezoe
6f30ffa865 Fixup 2016-03-05 10:51:45 +09:00
Naoki Takezoe
de3026248c Bump markedj to 1.0.7-SNAPSHOT 2016-03-05 10:31:27 +09:00
Herr Ritschwumm
413e75be5a generate checksums without ivy 2016-03-04 14:32:28 +01:00
Herr Ritschwumm
6a8ec18f9a generate sha256 checksum 2016-03-04 14:06:28 +01:00
Herr Ritschwumm
5b1b2ef3d7 tabs to spaces 2016-03-04 13:55:48 +01:00
Naoki Takezoe
9a705c62bf Merge pull request #1137 from marklacroix/fix-issue-comment-typo
Fix typo in issue comment list
2016-03-04 09:23:22 +09:00
Mark LaCroix
b103180bf6 Fix typo in issue comment list 2016-03-03 15:38:10 -05:00
Naoki Takezoe
536a0d3fe2 Merge pull request #1126 from gitbucket/add-test-info-to-run-doc
add info to run tests
2016-03-03 18:38:57 +09:00
Naoki Takezoe
cfcd250914 Introduction to quill 2016-03-03 18:36:52 +09:00
Matthieu Brouillard
356202e28a integrate xhub4j, fixes #1117 2016-03-02 22:25:21 +01:00
Naoki Takezoe
6db36e12b5 Merge pull request #1132 from McFoggy/issue-1128
correct path to CONTRIBUTING file, fixes #1128
2016-03-01 21:17:06 +09:00
Matthieu Brouillard
bfcd5a2855 correct path to CONTRIBUTING file, fixes #1128 2016-03-01 09:06:27 +01:00
Matthieu Brouillard
e218b52b78 add info to run tests 2016-03-01 01:10:19 +01:00
Naoki Takezoe
46998dc1fa Update release.md 2016-02-27 04:56:48 +09:00
62 changed files with 1394 additions and 1188 deletions

View File

@@ -1,6 +1,6 @@
### Before submitting an issue to Gitbucket I have first: ### Before submitting an issue to Gitbucket I have first:
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/CONTRIBUTING.md) - [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
- [] searched for similar already existing issue - [] searched for similar already existing issue
- [] read the documentation and [wiki](https://github.com/gitbucket/gitbucket/wiki) - [] read the documentation and [wiki](https://github.com/gitbucket/gitbucket/wiki)

View File

@@ -1,6 +1,6 @@
### Before submitting a pull-request to Gitbucket I have first: ### Before submitting a pull-request to Gitbucket I have first:
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/CONTRIBUTING.md) - [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
- [] rebased my branch over master - [] rebased my branch over master
- [] verified that project is compiling - [] verified that project is compiling
- [] verified that tests are passing - [] verified that tests are passing

View File

@@ -15,8 +15,7 @@ scalaVersion := "2.11.7"
// dependency settings // dependency settings
resolvers ++= Seq( resolvers ++= Seq(
Classpaths.typesafeReleases, Classpaths.typesafeReleases,
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/", "sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/"
"amateras-snapshot-repo" at "http://amateras.sourceforge.jp/mvn-snapshot/"
) )
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.1.1.201511131810-r", "org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.1.1.201511131810-r",
@@ -26,7 +25,7 @@ libraryDependencies ++= Seq(
"org.json4s" %% "json4s-jackson" % "3.3.0", "org.json4s" %% "json4s-jackson" % "3.3.0",
"io.github.gitbucket" %% "scalatra-forms" % "1.0.0", "io.github.gitbucket" %% "scalatra-forms" % "1.0.0",
"commons-io" % "commons-io" % "2.4", "commons-io" % "commons-io" % "2.4",
"io.github.gitbucket" % "markedj" % "1.0.6", "io.github.gitbucket" % "markedj" % "1.0.7",
"org.apache.commons" % "commons-compress" % "1.10", "org.apache.commons" % "commons-compress" % "1.10",
"org.apache.commons" % "commons-email" % "1.4", "org.apache.commons" % "commons-email" % "1.4",
"org.apache.httpcomponents" % "httpclient" % "4.5.1", "org.apache.httpcomponents" % "httpclient" % "4.5.1",
@@ -39,6 +38,8 @@ libraryDependencies ++= Seq(
"com.mchange" % "c3p0" % "0.9.5.2", "com.mchange" % "c3p0" % "0.9.5.2",
"com.typesafe" % "config" % "1.3.0", "com.typesafe" % "config" % "1.3.0",
"com.typesafe.akka" %% "akka-actor" % "2.3.14", "com.typesafe.akka" %% "akka-actor" % "2.3.14",
"io.getquill" %% "quill-jdbc" % "0.4.1",
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
"com.enragedginger" %% "akka-quartz-scheduler" % "1.4.0-akka-2.3.x" exclude("c3p0","c3p0"), "com.enragedginger" %% "akka-quartz-scheduler" % "1.4.0-akka-2.3.x" exclude("c3p0","c3p0"),
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided", "org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided", "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
@@ -82,36 +83,36 @@ jrebelSettings
// Create executable war file // Create executable war file
val executableConfig = config("executable").hide val executableConfig = config("executable").hide
Keys.ivyConfigurations += executableConfig Keys.ivyConfigurations += executableConfig
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.eclipse.jetty" % "jetty-security" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-security" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-continuation" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-continuation" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-server" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-server" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-xml" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-xml" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-http" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-http" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-servlet" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-servlet" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-io" % JettyVersion % "executable", "org.eclipse.jetty" % "jetty-io" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-util" % JettyVersion % "executable" "org.eclipse.jetty" % "jetty-util" % JettyVersion % "executable"
) )
val executableKey = TaskKey[File]("executable") val executableKey = TaskKey[File]("executable")
executableKey := { executableKey := {
import org.apache.ivy.util.ChecksumHelper import org.apache.ivy.util.ChecksumHelper
import java.util.jar.{ Manifest => JarManifest } import java.util.jar.{ Manifest => JarManifest }
import java.util.jar.Attributes.{ Name => AttrName } import java.util.jar.Attributes.{ Name => AttrName }
val workDir = Keys.target.value / "executable" val workDir = Keys.target.value / "executable"
val warName = Keys.name.value + ".war" val warName = Keys.name.value + ".war"
val log = streams.value.log val log = streams.value.log
log info s"building executable webapp in ${workDir}" log info s"building executable webapp in ${workDir}"
// initialize temp directory // initialize temp directory
val temp = workDir / "webapp" val temp = workDir / "webapp"
IO delete temp IO delete temp
// include jetty classes // include jetty classes
val jettyJars = Keys.update.value select configurationFilter(name = executableConfig.name) val jettyJars = Keys.update.value select configurationFilter(name = executableConfig.name)
jettyJars foreach { jar => jettyJars foreach { jar =>
IO unzip (jar, temp, (name:String) => IO unzip (jar, temp, (name:String) =>
(name startsWith "javax/") || (name startsWith "javax/") ||
@@ -120,31 +121,34 @@ executableKey := {
} }
// include original war file // include original war file
val warFile = (Keys.`package`).value val warFile = (Keys.`package`).value
IO unzip (warFile, temp) IO unzip (warFile, temp)
// include launcher classes // include launcher classes
val classDir = (Keys.classDirectory in Compile).value val classDir = (Keys.classDirectory in Compile).value
val launchClasses = Seq("JettyLauncher.class" /*, "HttpsSupportConnector.class" */) val launchClasses = Seq("JettyLauncher.class" /*, "HttpsSupportConnector.class" */)
launchClasses foreach { name => launchClasses foreach { name =>
IO copyFile (classDir / name, temp / name) IO copyFile (classDir / name, temp / name)
} }
// zip it up // zip it up
IO delete (temp / "META-INF" / "MANIFEST.MF") IO delete (temp / "META-INF" / "MANIFEST.MF")
val contentMappings = (temp.*** --- PathFinder(temp)).get pair relativeTo(temp) val contentMappings = (temp.*** --- PathFinder(temp)).get pair relativeTo(temp)
val manifest = new JarManifest val manifest = new JarManifest
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0") manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher") manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
val outputFile = workDir / warName val outputFile = workDir / warName
IO jar (contentMappings, outputFile, manifest) IO jar (contentMappings, outputFile, manifest)
// generate checksums // generate checksums
Seq("md5", "sha1") foreach { algorithm => Seq(
IO.write( "md5" -> "MD5",
workDir / (warName + "." + algorithm), "sha1" -> "SHA-1",
ChecksumHelper computeAsString (outputFile, algorithm) "sha256" -> "SHA-256"
) )
.foreach { case (extension, algorithm) =>
val checksumFile = workDir / (warName + "." + extension)
Checksums generate (outputFile, checksumFile, algorithm)
} }
// done // done
@@ -153,7 +157,7 @@ executableKey := {
} }
/* /*
Keys.artifact in (Compile, executableKey) ~= { Keys.artifact in (Compile, executableKey) ~= {
_ copy (`type` = "war", extension = "war")) _ copy (`type` = "war", extension = "war"))
} }
addArtifact(Keys.artifact in (Compile, executableKey), executableKey) addArtifact(Keys.artifact in (Compile, executableKey), executableKey)
*/ */

View File

@@ -32,3 +32,11 @@ $ sbt executable
``` ```
at the top of the source tree. It generates executable `gitbucket.war` into `target/executable`. We release this war file as release artifact. at the top of the source tree. It generates executable `gitbucket.war` into `target/executable`. We release this war file as release artifact.
Run tests spec
---------
To run the full serie of tests, run the following command:
```
sbt test
```

View File

@@ -6,15 +6,14 @@ Update version number
Note to update version number in files below: Note to update version number in files below:
### project/build.scala ### build.sbt
```scala ```scala
object MyBuild extends Build { val Organization = "gitbucket"
val Organization = "gitbucket" val Name = "gitbucket"
val Name = "gitbucket" val GitBucketVersion = "3.12.0" // <---- update version!!
val Version = "3.3.0" // <---- update version!! val ScalatraVersion = "2.4.0"
val ScalaVersion = "2.11.6" val JettyVersion = "9.3.6.v20151106"
val ScalatraVersion = "2.3.1"
``` ```
### src/main/scala/gitbucket/core/servlet/AutoUpdate.scala ### src/main/scala/gitbucket/core/servlet/AutoUpdate.scala
@@ -26,8 +25,8 @@ object AutoUpdate {
* The history of versions. A head of this sequence is the current GitBucket version. * The history of versions. A head of this sequence is the current GitBucket version.
*/ */
val versions = Seq( val versions = Seq(
new Version(3, 3), // <---- add this line!! new Version(3, 12), // <---- add this line!!
new Version(3, 2), new Version(3, 11),
``` ```
Generate release files Generate release files

34
project/Checksums.scala Normal file
View File

@@ -0,0 +1,34 @@
import java.security.MessageDigest;
import scala.annotation._
import sbt._
import sbt.Using._
object Checksums {
private val bufferSize = 2048
def generate(source:File, target:File, algorithm:String):Unit =
IO write (target, compute(source, algorithm))
def compute(file:File, algorithm:String):String =
hex(raw(file, algorithm))
def raw(file:File, algorithm:String):Array[Byte] =
(Using fileInputStream file) { is =>
val md = MessageDigest getInstance algorithm
val buf = new Array[Byte](bufferSize)
md.reset()
@tailrec
def loop() {
val len = is read buf
if (len != -1) {
md update (buf, 0, len)
loop()
}
}
loop()
md.digest()
}
def hex(bytes:Array[Byte]):String =
bytes map { it => "%02x" format (it.toInt & 0xff) } mkString ""
}

View File

@@ -0,0 +1,8 @@
db.dataSourceClassName="org.h2.jdbcx.JdbcDataSource"
db.dataSource.url="jdbc:h2:~/.gitbucket/data;MVCC=true"
db.dataSource.user="sa"
db.dataSource.password="sa"
#db.dataSource.cachePrepStmts=true
#db.dataSource.prepStmtCacheSize=250
#db.dataSource.prepStmtCacheSqlLimit=2048
#db.connectionTimeout=30000

View File

@@ -0,0 +1 @@
ALTER TABLE WEB_HOOK ADD COLUMN TOKEN VARCHAR(100);

View File

@@ -27,10 +27,10 @@ class ScalatraBootstrap extends LifeCycle {
} }
context.mount(new IndexController, "/") context.mount(new IndexController, "/")
context.mount(new ApiController, "/api/v3")
context.mount(new FileUploadController, "/upload") context.mount(new FileUploadController, "/upload")
context.mount(new SystemSettingsController, "/admin")
context.mount(new DashboardController, "/*") context.mount(new DashboardController, "/*")
context.mount(new UserManagementController, "/*")
context.mount(new SystemSettingsController, "/*")
context.mount(new AccountController, "/*") context.mount(new AccountController, "/*")
context.mount(new RepositoryViewerController, "/*") context.mount(new RepositoryViewerController, "/*")
context.mount(new WikiController, "/*") context.mount(new WikiController, "/*")

View File

@@ -39,7 +39,7 @@ object ApiRepository{
description = repository.description.getOrElse(""), description = repository.description.getOrElse(""),
watchers = 0, watchers = 0,
forks = forkedCount, forks = forkedCount,
`private` = repository.isPrivate, `private` = repository.`private`,
default_branch = repository.defaultBranch, default_branch = repository.defaultBranch,
owner = owner owner = owner
)(urlIsHtmlUrl) )(urlIsHtmlUrl)

View File

@@ -29,8 +29,8 @@ object ApiUser{
def apply(user: Account): ApiUser = ApiUser( def apply(user: Account): ApiUser = ApiUser(
login = user.userName, login = user.userName,
email = user.mailAddress, email = user.mailAddress,
`type` = if(user.isGroupAccount){ "Organization" }else{ "User" }, `type` = if(user.groupAccount){ "Organization" }else{ "User" },
site_admin = user.isAdmin, site_admin = user.administrator,
created_at = user.registeredDate created_at = user.registeredDate
) )
} }

View File

@@ -1,7 +1,6 @@
package gitbucket.core.controller package gitbucket.core.controller
import gitbucket.core.account.html import gitbucket.core.account.html
import gitbucket.core.api._
import gitbucket.core.helper import gitbucket.core.helper
import gitbucket.core.model.GroupMember import gitbucket.core.model.GroupMember
import gitbucket.core.service._ import gitbucket.core.service._
@@ -14,22 +13,19 @@ import gitbucket.core.util._
import io.github.gitbucket.scalatra.forms._ import io.github.gitbucket.scalatra.forms._
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.DirCache
import org.eclipse.jgit.lib.{FileMode, Constants}
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
class AccountController extends AccountControllerBase class AccountController extends AccountControllerBase
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
with AccessTokenService with WebHookService with AccessTokenService with WebHookService with RepositoryCreationService
trait AccountControllerBase extends AccountManagementControllerBase { trait AccountControllerBase extends AccountManagementControllerBase {
self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
with AccessTokenService with WebHookService => with AccessTokenService with WebHookService with RepositoryCreationService =>
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String, case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
url: Option[String], fileId: Option[String]) url: Option[String], fileId: Option[String])
@@ -118,23 +114,23 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// Public Activity // Public Activity
case "activity" => case "activity" =>
gitbucket.core.account.html.activity(account, gitbucket.core.account.html.activity(account,
if(account.isGroupAccount) Nil else getGroupsByUserName(userName), if(account.groupAccount) Nil else getGroupsByUserName(userName),
getActivitiesByUser(userName, true)) getActivitiesByUser(userName, true))
// Members // Members
case "members" if(account.isGroupAccount) => { case "members" if(account.groupAccount) => {
val members = getGroupMembers(account.userName) val members = getGroupMembers(account.userName)
gitbucket.core.account.html.members(account, members.map(_.userName), gitbucket.core.account.html.members(account, members.map(_.userName),
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })) context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.manager }))
} }
// Repositories // Repositories
case _ => { case _ => {
val members = getGroupMembers(account.userName) val members = getGroupMembers(account.userName)
gitbucket.core.account.html.repositories(account, gitbucket.core.account.html.repositories(account,
if(account.isGroupAccount) Nil else getGroupsByUserName(userName), if(account.groupAccount) Nil else getGroupsByUserName(userName),
getVisibleRepositories(context.loginAccount, Some(userName)), getVisibleRepositories(context.loginAccount, Some(userName)),
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })) context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.manager }))
} }
} }
} getOrElse NotFound } getOrElse NotFound
@@ -156,25 +152,6 @@ trait AccountControllerBase extends AccountManagementControllerBase {
} }
} }
/**
* https://developer.github.com/v3/users/#get-a-single-user
*/
get("/api/v3/users/:userName") {
getAccountByUserName(params("userName")).map { account =>
JsonFormat(ApiUser(account))
} getOrElse NotFound
}
/**
* https://developer.github.com/v3/users/#get-the-authenticated-user
*/
get("/api/v3/user") {
context.loginAccount.map { account =>
JsonFormat(ApiUser(account))
} getOrElse Unauthorized
}
get("/:userName/_edit")(oneselfOnly { get("/:userName/_edit")(oneselfOnly {
val userName = params("userName") val userName = params("userName")
getAccountByUserName(userName).map { x => getAccountByUserName(userName).map { x =>
@@ -213,7 +190,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// removeUserRelatedData(userName) // removeUserRelatedData(userName)
removeUserRelatedData(userName) removeUserRelatedData(userName)
updateAccount(account.copy(isRemoved = true)) updateAccount(account.copy(removed = true))
} }
session.invalidate session.invalidate
@@ -367,7 +344,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
post("/new", newRepositoryForm)(usersOnly { form => post("/new", newRepositoryForm)(usersOnly { form =>
LockUtil.lock(s"${form.owner}/${form.name}"){ LockUtil.lock(s"${form.owner}/${form.name}"){
if(getRepository(form.owner, form.name).isEmpty){ if(getRepository(form.owner, form.name).isEmpty){
createRepository(form.owner, form.name, form.description, form.isPrivate, form.createReadme) createRepository(context.loginAccount.get, form.owner, form.name, form.description, form.isPrivate, form.createReadme)
} }
// redirect to the repository // redirect to the repository
@@ -375,54 +352,6 @@ trait AccountControllerBase extends AccountManagementControllerBase {
} }
}) })
/**
* Create user repository
* https://developer.github.com/v3/repos/#create
*/
post("/api/v3/user/repos")(usersOnly {
val owner = context.loginAccount.get.userName
(for {
data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield {
LockUtil.lock(s"${owner}/${data.name}") {
if(getRepository(owner, data.name).isEmpty){
createRepository(owner, data.name, data.description, data.`private`, data.auto_init)
val repository = getRepository(owner, data.name).get
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
} else {
ApiError(
"A repository with this name already exists on this account",
Some("https://developer.github.com/v3/repos/#create")
)
}
}
}) getOrElse NotFound
})
/**
* Create group repository
* https://developer.github.com/v3/repos/#create
*/
post("/api/v3/orgs/:org/repos")(managersOnly {
val groupName = params("org")
(for {
data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield {
LockUtil.lock(s"${groupName}/${data.name}") {
if(getRepository(groupName, data.name).isEmpty){
createRepository(groupName, data.name, data.description, data.`private`, data.auto_init)
val repository = getRepository(groupName, data.name).get
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
} else {
ApiError(
"A repository with this name already exists for this group",
Some("https://developer.github.com/v3/repos/#create")
)
}
}
}) getOrElse NotFound
})
get("/:owner/:repository/fork")(readableUsersOnly { repository => get("/:owner/:repository/fork")(readableUsersOnly { repository =>
val loginAccount = context.loginAccount.get val loginAccount = context.loginAccount.get
val loginUserName = loginAccount.userName val loginUserName = loginAccount.userName
@@ -431,7 +360,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
case _: List[String] => case _: List[String] =>
val managerPermissions = groups.map { group => val managerPermissions = groups.map { group =>
val members = getGroupMembers(group) val members = getGroupMembers(group)
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }) context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.manager })
} }
helper.html.forkrepository( helper.html.forkrepository(
repository, repository,
@@ -456,11 +385,11 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val originUserName = repository.repository.originUserName.getOrElse(repository.owner) val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name) val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name)
createRepository( insertRepository(
repositoryName = repository.name, repositoryName = repository.name,
userName = accountName, userName = accountName,
description = repository.repository.description, description = repository.repository.description,
isPrivate = repository.repository.isPrivate, isPrivate = repository.repository.`private`,
originRepositoryName = Some(originRepositoryName), originRepositoryName = Some(originRepositoryName),
originUserName = Some(originUserName), originUserName = Some(originUserName),
parentRepositoryName = Some(repository.name), parentRepositoryName = Some(repository.name),
@@ -469,7 +398,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// Add collaborators for group repository // Add collaborators for group repository
val ownerAccount = getAccountByUserName(accountName).get val ownerAccount = getAccountByUserName(accountName).get
if(ownerAccount.isGroupAccount){ if(ownerAccount.groupAccount){
getGroupMembers(accountName).foreach { member => getGroupMembers(accountName).foreach { member =>
addCollaborator(accountName, repository.name, member.userName) addCollaborator(accountName, repository.name, member.userName)
} }
@@ -496,68 +425,6 @@ trait AccountControllerBase extends AccountManagementControllerBase {
} }
}) })
private def createRepository(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean) {
val ownerAccount = getAccountByUserName(owner).get
val loginAccount = context.loginAccount.get
val loginUserName = loginAccount.userName
// Insert to the database at first
createRepository(name, owner, description, isPrivate)
// Add collaborators for group repository
if(ownerAccount.isGroupAccount){
getGroupMembers(owner).foreach { member =>
addCollaborator(owner, name, member.userName)
}
}
// Insert default labels
insertDefaultLabels(owner, name)
// Create the actual repository
val gitdir = getRepositoryDir(owner, name)
JGitUtil.initRepository(gitdir)
if(createReadme){
using(Git.open(gitdir)){ git =>
val builder = DirCache.newInCore.builder()
val inserter = git.getRepository.newObjectInserter()
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
val content = if(description.nonEmpty){
name + "\n" +
"===============\n" +
"\n" +
description.get
} else {
name + "\n" +
"===============\n"
}
builder.add(JGitUtil.createDirCacheEntry("README.md", FileMode.REGULAR_FILE,
inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
builder.finish()
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit")
}
}
// Create Wiki repository
createWikiRepository(loginAccount, owner, name)
// Record activity
recordCreateRepositoryActivity(owner, name, loginUserName)
}
private def insertDefaultLabels(userName: String, repositoryName: String): Unit = {
createLabel(userName, repositoryName, "bug", "fc2929")
createLabel(userName, repositoryName, "duplicate", "cccccc")
createLabel(userName, repositoryName, "enhancement", "84b6eb")
createLabel(userName, repositoryName, "invalid", "e6e6e6")
createLabel(userName, repositoryName, "question", "cc317c")
createLabel(userName, repositoryName, "wontfix", "ffffff")
}
private def existsAccount: Constraint = new Constraint(){ private def existsAccount: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] = override def validate(name: String, value: String, messages: Messages): Option[String] =
if(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None if(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None

View File

@@ -0,0 +1,389 @@
package gitbucket.core.controller
import gitbucket.core.api._
import gitbucket.core.model._
import gitbucket.core.service.IssuesService.IssueSearchCondition
import gitbucket.core.service.PullRequestService._
import gitbucket.core.service._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util._
import gitbucket.core.util.Implicits._
import org.eclipse.jgit.api.Git
import org.scalatra.{NoContent, UnprocessableEntity, Created}
import scala.collection.JavaConverters._
class ApiController extends ApiControllerBase
with RepositoryService
with AccountService
with ProtectedBranchService
with IssuesService
with LabelsService
with PullRequestService
with CommitStatusService
with RepositoryCreationService
with HandleCommentService
with WebHookService
with WebHookPullRequestService
with WebHookIssueCommentService
with WikiService
with ActivityService
with OwnerAuthenticator
with UsersAuthenticator
with GroupManagerAuthenticator
with ReferrerAuthenticator
with ReadableUsersAuthenticator
with CollaboratorsAuthenticator
trait ApiControllerBase extends ControllerBase {
self: RepositoryService
with AccountService
with ProtectedBranchService
with IssuesService
with LabelsService
with PullRequestService
with CommitStatusService
with RepositoryCreationService
with HandleCommentService
with OwnerAuthenticator
with UsersAuthenticator
with GroupManagerAuthenticator
with ReferrerAuthenticator
with ReadableUsersAuthenticator
with CollaboratorsAuthenticator =>
/**
* https://developer.github.com/v3/users/#get-a-single-user
*/
get("/api/v3/users/:userName") {
getAccountByUserName(params("userName")).map { account =>
JsonFormat(ApiUser(account))
} getOrElse NotFound
}
/**
* https://developer.github.com/v3/users/#get-the-authenticated-user
*/
get("/api/v3/user") {
context.loginAccount.map { account =>
JsonFormat(ApiUser(account))
} getOrElse Unauthorized
}
/**
* Create user repository
* https://developer.github.com/v3/repos/#create
*/
post("/api/v3/user/repos")(usersOnly {
val owner = context.loginAccount.get.userName
(for {
data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield {
LockUtil.lock(s"${owner}/${data.name}") {
if(getRepository(owner, data.name).isEmpty){
createRepository(context.loginAccount.get, owner, data.name, data.description, data.`private`, data.auto_init)
val repository = getRepository(owner, data.name).get
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
} else {
ApiError(
"A repository with this name already exists on this account",
Some("https://developer.github.com/v3/repos/#create")
)
}
}
}) getOrElse NotFound
})
/**
* Create group repository
* https://developer.github.com/v3/repos/#create
*/
post("/api/v3/orgs/:org/repos")(managersOnly {
val groupName = params("org")
(for {
data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield {
LockUtil.lock(s"${groupName}/${data.name}") {
if(getRepository(groupName, data.name).isEmpty){
createRepository(context.loginAccount.get, groupName, data.name, data.description, data.`private`, data.auto_init)
val repository = getRepository(groupName, data.name).get
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
} else {
ApiError(
"A repository with this name already exists for this group",
Some("https://developer.github.com/v3/repos/#create")
)
}
}
}) getOrElse NotFound
})
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
import gitbucket.core.api._
(for{
branch <- params.get("branch") if repository.branchList.find(_ == branch).isDefined
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
} yield {
if(protection.enabled){
enableBranchProtection(repository.owner, repository.name, branch, protection.status.enforcement_level == ApiBranchProtection.Everyone, protection.status.contexts)
} else {
disableBranchProtection(repository.owner, repository.name, branch)
}
JsonFormat(ApiBranch(branch, protection)(RepositoryName(repository)))
}) getOrElse NotFound
})
/**
* @see https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status
* but not enabled.
*/
get("/api/v3/rate_limit"){
contentType = formats("json")
// this message is same as github enterprise...
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
}
/**
* https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
*/
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
(for{
issueId <- params("id").toIntOpt
comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt)
} yield {
JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
}).getOrElse(NotFound)
})
/**
* https://developer.github.com/v3/issues/comments/#create-a-comment
*/
post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository =>
(for{
issueId <- params("id").toIntOpt
issue <- getIssue(repository.owner, repository.name, issueId.toString)
body <- extractFromJsonBody[CreateAComment].map(_.body) if ! body.isEmpty
action = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
(issue, id) <- handleComment(issue, Some(body), repository, action)
issueComment <- getComment(repository.owner, repository.name, id.toString())
} yield {
JsonFormat(ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(context.loginAccount.get), issue.isPullRequest))
}) getOrElse NotFound
})
/**
* List all labels for this repository
* https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository
*/
get("/api/v3/repos/:owner/:repository/labels")(referrersOnly { repository =>
JsonFormat(getLabels(repository.owner, repository.name).map { label =>
ApiLabel(label, RepositoryName(repository))
})
})
/**
* Get a single label
* https://developer.github.com/v3/issues/labels/#get-a-single-label
*/
get("/api/v3/repos/:owner/:repository/labels/:labelName")(referrersOnly { repository =>
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
JsonFormat(ApiLabel(label, RepositoryName(repository)))
} getOrElse NotFound()
})
/**
* Create a label
* https://developer.github.com/v3/issues/labels/#create-a-label
*/
post("/api/v3/repos/:owner/:repository/labels")(collaboratorsOnly { repository =>
(for{
data <- extractFromJsonBody[CreateALabel] if data.isValid
} yield {
LockUtil.lock(RepositoryName(repository).fullName) {
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
val labelId = createLabel(repository.owner, repository.name, data.name, data.color)
getLabel(repository.owner, repository.name, labelId).map { label =>
Created(JsonFormat(ApiLabel(label, RepositoryName(repository))))
} getOrElse NotFound()
} else {
// TODO ApiError should support errors field to enhance compatibility of GitHub API
UnprocessableEntity(ApiError(
"Validation Failed",
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
))
}
}
}) getOrElse NotFound()
})
/**
* Update a label
* https://developer.github.com/v3/issues/labels/#update-a-label
*/
patch("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
(for{
data <- extractFromJsonBody[CreateALabel] if data.isValid
} yield {
LockUtil.lock(RepositoryName(repository).fullName) {
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
JsonFormat(ApiLabel(
getLabel(repository.owner, repository.name, label.labelId).get,
RepositoryName(repository)))
} else {
// TODO ApiError should support errors field to enhance compatibility of GitHub API
UnprocessableEntity(ApiError(
"Validation Failed",
Some("https://developer.github.com/v3/issues/labels/#create-a-label")))
}
} getOrElse NotFound()
}
}) getOrElse NotFound()
})
/**
* Delete a label
* https://developer.github.com/v3/issues/labels/#delete-a-label
*/
delete("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
LockUtil.lock(RepositoryName(repository).fullName) {
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
deleteLabel(repository.owner, repository.name, label.labelId)
NoContent()
} getOrElse NotFound()
}
})
/**
* https://developer.github.com/v3/pulls/#list-pull-requests
*/
get("/api/v3/repos/:owner/:repository/pulls")(referrersOnly { repository =>
val page = IssueSearchCondition.page(request)
// TODO: more api spec condition
val condition = IssueSearchCondition(request)
val baseOwner = getAccountByUserName(repository.owner).get
val issues:List[(Issue, Account, Int, PullRequest, Repository, Account)] = searchPullRequestByApi(condition, (page - 1) * PullRequestLimit, PullRequestLimit, repository.owner -> repository.name)
JsonFormat(issues.map{case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) =>
ApiPullRequest(
issue,
pullRequest,
ApiRepository(headRepo, ApiUser(headOwner)),
ApiRepository(repository, ApiUser(baseOwner)),
ApiUser(issueUser)) })
})
/**
* https://developer.github.com/v3/pulls/#get-a-single-pull-request
*/
get("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository =>
(for{
issueId <- params("id").toIntOpt
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set())
baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName)
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
} yield {
JsonFormat(ApiPullRequest(
issue,
pullRequest,
ApiRepository(headRepo, ApiUser(headOwner)),
ApiRepository(repository, ApiUser(baseOwner)),
ApiUser(issueUser)))
}).getOrElse(NotFound)
})
/**
* https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request
*/
get("/api/v3/repos/:owner/:repository/pulls/:id/commits")(referrersOnly { repository =>
val owner = repository.owner
val name = repository.name
params("id").toIntOpt.flatMap{ issueId =>
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
using(Git.open(getRepositoryDir(owner, name))){ git =>
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
val newId = git.getRepository.resolve(pullreq.commitIdTo)
val repoFullName = RepositoryName(repository)
val commits = git.log.addRange(oldId, newId).call.iterator.asScala.map(c => ApiCommitListItem(new CommitInfo(c), repoFullName)).toList
JsonFormat(commits)
}
}
} getOrElse NotFound
})
/**
* https://developer.github.com/v3/repos/#get
*/
get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
})
/**
* https://developer.github.com/v3/repos/statuses/#create-a-status
*/
post("/api/v3/repos/:owner/:repo/statuses/:sha")(collaboratorsOnly { repository =>
(for{
ref <- params.get("sha")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
data <- extractFromJsonBody[CreateAStatus] if data.isValid
creator <- context.loginAccount
state <- CommitState.valueOf(data.state)
statusId = createCommitStatus(repository.owner, repository.name, sha, data.context.getOrElse("default"),
state, data.target_url, data.description, new java.util.Date(), creator)
status <- getCommitStatus(repository.owner, repository.name, statusId)
} yield {
JsonFormat(ApiCommitStatus(status, ApiUser(creator)))
}) getOrElse NotFound
})
/**
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
*
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
*/
val listStatusesRoute = get("/api/v3/repos/:owner/:repo/commits/:ref/statuses")(referrersOnly { repository =>
(for{
ref <- params.get("ref")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield {
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map{ case(status, creator) =>
ApiCommitStatus(status, ApiUser(creator))
})
}) getOrElse NotFound
})
/**
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
*
* legacy route
*/
get("/api/v3/repos/:owner/:repo/statuses/:ref"){
listStatusesRoute.action()
}
/**
* https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
*
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
*/
get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository =>
(for{
ref <- params.get("ref")
owner <- getAccountByUserName(repository.owner)
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield {
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
}) getOrElse NotFound
})
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
}

View File

@@ -50,7 +50,7 @@ abstract class ControllerBase extends ScalatraFilter
if(account == null){ if(account == null){
// Redirect to login form // Redirect to login form
httpResponse.sendRedirect(baseUrl + "/signin?redirect=" + StringUtil.urlEncode(path)) httpResponse.sendRedirect(baseUrl + "/signin?redirect=" + StringUtil.urlEncode(path))
} else if(account.isAdmin){ } else if(account.administrator){
// H2 Console (administrators only) // H2 Console (administrators only)
chain.doFilter(request, response) chain.doFilter(request, response)
} else { } else {

View File

@@ -110,7 +110,7 @@ trait IndexControllerBase extends ControllerBase {
get("/_user/proposals")(usersOnly { get("/_user/proposals")(usersOnly {
contentType = formats("json") contentType = formats("json")
org.json4s.jackson.Serialization.write( org.json4s.jackson.Serialization.write(
Map("options" -> getAllUsers(false).filter(!_.isGroupAccount).map(_.userName).toArray) Map("options" -> getAllUsers(false).filter(!_.groupAccount).map(_.userName).toArray)
) )
}) })
@@ -121,16 +121,6 @@ trait IndexControllerBase extends ControllerBase {
getAccountByUserName(params("userName")).isDefined getAccountByUserName(params("userName")).isDefined
}) })
/**
* @see https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status
* but not enabled.
*/
get("/api/v3/rate_limit"){
contentType = formats("json")
// this message is same as github enterprise...
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
}
// TODO Move to RepositoryViwerController? // TODO Move to RepositoryViwerController?
post("/search", searchForm){ form => post("/search", searchForm){ form =>
redirect(s"/${form.owner}/${form.repository}/search?q=${StringUtil.urlEncode(form.query)}") redirect(s"/${form.owner}/${form.repository}/search?q=${StringUtil.urlEncode(form.query)}")

View File

@@ -1,8 +1,6 @@
package gitbucket.core.controller package gitbucket.core.controller
import gitbucket.core.api._
import gitbucket.core.issues.html import gitbucket.core.issues.html
import gitbucket.core.model.Issue
import gitbucket.core.service.IssuesService._ import gitbucket.core.service.IssuesService._
import gitbucket.core.service._ import gitbucket.core.service._
import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.ControlUtil._
@@ -16,11 +14,11 @@ import org.scalatra.Ok
class IssuesController extends IssuesControllerBase class IssuesController extends IssuesControllerBase
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with HandleCommentService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService
trait IssuesControllerBase extends ControllerBase { trait IssuesControllerBase extends ControllerBase {
self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with HandleCommentService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService => with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService =>
case class IssueCreateForm(title: String, content: Option[String], case class IssueCreateForm(title: String, content: Option[String],
@@ -69,7 +67,7 @@ trait IssuesControllerBase extends ControllerBase {
_, _,
getComments(owner, name, issueId.toInt), getComments(owner, name, issueId.toInt),
getIssueLabels(owner, name, issueId.toInt), getIssueLabels(owner, name, issueId.toInt),
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted, (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.groupAccount) Nil else List(owner))).sorted,
getMilestonesWithIssueCount(owner, name), getMilestonesWithIssueCount(owner, name),
getLabels(owner, name), getLabels(owner, name),
hasWritePermission(owner, name, context.loginAccount), hasWritePermission(owner, name, context.loginAccount),
@@ -78,22 +76,10 @@ trait IssuesControllerBase extends ControllerBase {
} }
}) })
/**
* https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
*/
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
(for{
issueId <- params("id").toIntOpt
comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt)
} yield {
JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
}).getOrElse(NotFound)
})
get("/:owner/:repository/issues/new")(readableUsersOnly { repository => get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
defining(repository.owner, repository.name){ case (owner, name) => defining(repository.owner, repository.name){ case (owner, name) =>
html.create( html.create(
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted, (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.groupAccount) Nil else List(owner))).sorted,
getMilestones(owner, name), getMilestones(owner, name),
getLabels(owner, name), getLabels(owner, name),
hasWritePermission(owner, name, context.loginAccount), hasWritePermission(owner, name, context.loginAccount),
@@ -128,7 +114,7 @@ trait IssuesControllerBase extends ControllerBase {
getIssue(owner, name, issueId.toString).foreach { issue => getIssue(owner, name, issueId.toString).foreach { issue =>
// extract references and create refer comment // extract references and create refer comment
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse("")) createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get)
// call web hooks // call web hooks
callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get) callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get)
@@ -150,7 +136,7 @@ trait IssuesControllerBase extends ControllerBase {
// update issue // update issue
updateIssue(owner, name, issue.issueId, title, issue.content) updateIssue(owner, name, issue.issueId, title, issue.content)
// extract references and create refer comment // extract references and create refer comment
createReferComment(owner, name, issue.copy(title = title), title) createReferComment(owner, name, issue.copy(title = title), title, context.loginAccount.get)
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}") redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
} else Unauthorized } else Unauthorized
@@ -165,7 +151,7 @@ trait IssuesControllerBase extends ControllerBase {
// update issue // update issue
updateIssue(owner, name, issue.issueId, issue.title, content) updateIssue(owner, name, issue.issueId, issue.title, content)
// extract references and create refer comment // extract references and create refer comment
createReferComment(owner, name, issue, content.getOrElse("")) createReferComment(owner, name, issue, content.getOrElse(""), context.loginAccount.get)
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}") redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
} else Unauthorized } else Unauthorized
@@ -174,30 +160,22 @@ trait IssuesControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
handleComment(form.issueId, Some(form.content), repository)() map { case (issue, id) => getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
redirect(s"/${repository.owner}/${repository.name}/${ val actionOpt = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}") handleComment(issue, Some(form.content), repository, actionOpt) map { case (issue, id) =>
redirect(s"/${repository.owner}/${repository.name}/${
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
}
} getOrElse NotFound } getOrElse NotFound
}) })
/**
* https://developer.github.com/v3/issues/comments/#create-a-comment
*/
post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository =>
(for{
issueId <- params("id").toIntOpt
body <- extractFromJsonBody[CreateAComment].map(_.body) if ! body.isEmpty
(issue, id) <- handleComment(issueId, Some(body), repository)()
issueComment <- getComment(repository.owner, repository.name, id.toString())
} yield {
JsonFormat(ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(context.loginAccount.get), issue.isPullRequest))
}) getOrElse NotFound
})
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) => post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
handleComment(form.issueId, form.content, repository)() map { case (issue, id) => getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
redirect(s"/${repository.owner}/${repository.name}/${ val actionOpt = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}") handleComment(issue, form.content, repository, actionOpt) map { case (issue, id) =>
redirect(s"/${repository.owner}/${repository.name}/${
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
}
} getOrElse NotFound } getOrElse NotFound
}) })
@@ -315,8 +293,16 @@ trait IssuesControllerBase extends ControllerBase {
post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository => post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
defining(params.get("value")){ action => defining(params.get("value")){ action =>
action match { action match {
case Some("open") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("reopen")) } case Some("open") => executeBatch(repository) { issueId =>
case Some("close") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("close")) } getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
handleComment(issue, None, repository, Some("reopen"))
}
}
case Some("close") => executeBatch(repository) { issueId =>
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
handleComment(issue, None, repository, Some("close"))
}
}
case _ => // TODO BadRequest case _ => // TODO BadRequest
} }
} }
@@ -373,99 +359,6 @@ trait IssuesControllerBase extends ControllerBase {
} }
} }
// TODO Same method exists in PullRequestController. Should it moved to IssueService?
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
StringUtil.extractIssueId(message).foreach { issueId =>
val content = fromIssue.issueId + ":" + fromIssue.title
if(getIssue(owner, repository, issueId).isDefined){
// Not add if refer comment already exist.
if(!getComments(owner, repository, issueId.toInt).exists { x => x.action == "refer" && x.content == content }) {
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt, content, "refer")
}
}
}
}
/**
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
*/
private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo)
(getAction: Issue => Option[String] =
p1 => params.get("action").filter(_ => isEditable(p1.userName, p1.repositoryName, p1.openedUserName))) = {
defining(repository.owner, repository.name){ case (owner, name) =>
val userName = context.loginAccount.get.userName
getIssue(owner, name, issueId.toString) flatMap { issue =>
val (action, recordActivity) =
getAction(issue)
.collect {
case "close" if(!issue.closed) => true ->
(Some("close") -> Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
case "reopen" if(issue.closed) => false ->
(Some("reopen") -> Some(recordReopenIssueActivity _))
}
.map { case (closed, t) =>
updateClosed(owner, name, issueId, closed)
t
}
.getOrElse(None -> None)
val commentId = (content, action) match {
case (None, None) => None
case (None, Some(action)) => Some(createComment(owner, name, userName, issueId, action.capitalize, action))
case (Some(content), _) => Some(createComment(owner, name, userName, issueId, content, action.map(_+ "_comment").getOrElse("comment")))
}
// record comment activity if comment is entered
content foreach {
(if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
(owner, name, userName, issueId, _)
}
recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
// extract references and create refer comment
content.map { content =>
createReferComment(owner, name, issue, content)
}
// call web hooks
action match {
case None => commentId.map{ commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, context.loginAccount.get) }
case Some(act) => val webHookAction = act match {
case "open" => "opened"
case "reopen" => "reopened"
case "close" => "closed"
case _ => act
}
if(issue.isPullRequest){
callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, context.loginAccount.get)
} else {
callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, context.loginAccount.get)
}
}
// notifications
Notifier() match {
case f =>
content foreach {
f.toNotify(repository, issue, _){
Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId.get}")
}
}
action foreach {
f.toNotify(repository, issue, _){
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
}
}
}
commentId.map( issue -> _ )
}
}
}
private def searchIssues(repository: RepositoryService.RepositoryInfo) = { private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
defining(repository.owner, repository.name){ case (owner, repoName) => defining(repository.owner, repository.name){ case (owner, repoName) =>
val page = IssueSearchCondition.page(request) val page = IssueSearchCondition.page(request)
@@ -487,7 +380,7 @@ trait IssuesControllerBase extends ControllerBase {
"issues", "issues",
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName), searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
page, page,
if(!getAccountByUserName(owner).exists(_.isGroupAccount)){ if(!getAccountByUserName(owner).exists(_.groupAccount)){
(getCollaborators(owner, repoName) :+ owner).sorted (getCollaborators(owner, repoName) :+ owner).sorted
} else { } else {
getCollaborators(owner, repoName) getCollaborators(owner, repoName)

View File

@@ -1,13 +1,12 @@
package gitbucket.core.controller package gitbucket.core.controller
import gitbucket.core.api.{ApiError, CreateALabel, ApiLabel, JsonFormat}
import gitbucket.core.issues.labels.html import gitbucket.core.issues.labels.html
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService} import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
import gitbucket.core.util.{LockUtil, RepositoryName, ReferrerAuthenticator, CollaboratorsAuthenticator} import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import io.github.gitbucket.scalatra.forms._ import io.github.gitbucket.scalatra.forms._
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
import org.scalatra.{NoContent, UnprocessableEntity, Created, Ok} import org.scalatra.Ok
class LabelsController extends LabelsControllerBase class LabelsController extends LabelsControllerBase
with LabelsService with IssuesService with RepositoryService with AccountService with LabelsService with IssuesService with RepositoryService with AccountService
@@ -32,26 +31,6 @@ trait LabelsControllerBase extends ControllerBase {
hasWritePermission(repository.owner, repository.name, context.loginAccount)) hasWritePermission(repository.owner, repository.name, context.loginAccount))
}) })
/**
* List all labels for this repository
* https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository
*/
get("/api/v3/repos/:owner/:repository/labels")(referrersOnly { repository =>
JsonFormat(getLabels(repository.owner, repository.name).map { label =>
ApiLabel(label, RepositoryName(repository))
})
})
/**
* Get a single label
* https://developer.github.com/v3/issues/labels/#get-a-single-label
*/
get("/api/v3/repos/:owner/:repository/labels/:labelName")(referrersOnly { repository =>
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
JsonFormat(ApiLabel(label, RepositoryName(repository)))
} getOrElse NotFound()
})
ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository => ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository =>
html.edit(None, repository) html.edit(None, repository)
}) })
@@ -66,31 +45,6 @@ trait LabelsControllerBase extends ControllerBase {
hasWritePermission(repository.owner, repository.name, context.loginAccount)) hasWritePermission(repository.owner, repository.name, context.loginAccount))
}) })
/**
* Create a label
* https://developer.github.com/v3/issues/labels/#create-a-label
*/
post("/api/v3/repos/:owner/:repository/labels")(collaboratorsOnly { repository =>
(for{
data <- extractFromJsonBody[CreateALabel] if data.isValid
} yield {
LockUtil.lock(RepositoryName(repository).fullName) {
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
val labelId = createLabel(repository.owner, repository.name, data.name, data.color)
getLabel(repository.owner, repository.name, labelId).map { label =>
Created(JsonFormat(ApiLabel(label, RepositoryName(repository))))
} getOrElse NotFound()
} else {
// TODO ApiError should support errors field to enhance compatibility of GitHub API
UnprocessableEntity(ApiError(
"Validation Failed",
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
))
}
}
}) getOrElse NotFound()
})
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository => ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository =>
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label => getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
html.edit(Some(label), repository) html.edit(Some(label), repository)
@@ -107,50 +61,11 @@ trait LabelsControllerBase extends ControllerBase {
hasWritePermission(repository.owner, repository.name, context.loginAccount)) hasWritePermission(repository.owner, repository.name, context.loginAccount))
}) })
/**
* Update a label
* https://developer.github.com/v3/issues/labels/#update-a-label
*/
patch("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
(for{
data <- extractFromJsonBody[CreateALabel] if data.isValid
} yield {
LockUtil.lock(RepositoryName(repository).fullName) {
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
JsonFormat(ApiLabel(
getLabel(repository.owner, repository.name, label.labelId).get,
RepositoryName(repository)))
} else {
// TODO ApiError should support errors field to enhance compatibility of GitHub API
UnprocessableEntity(ApiError(
"Validation Failed",
Some("https://developer.github.com/v3/issues/labels/#create-a-label")))
}
} getOrElse NotFound()
}
}) getOrElse NotFound()
})
ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(collaboratorsOnly { repository => ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(collaboratorsOnly { repository =>
deleteLabel(repository.owner, repository.name, params("labelId").toInt) deleteLabel(repository.owner, repository.name, params("labelId").toInt)
Ok() Ok()
}) })
/**
* Delete a label
* https://developer.github.com/v3/issues/labels/#delete-a-label
*/
delete("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
LockUtil.lock(RepositoryName(repository).fullName) {
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
deleteLabel(repository.owner, repository.name, label.labelId)
NoContent()
} getOrElse NotFound()
}
})
/** /**
* Constraint for the identifier such as user name, repository name or page name. * Constraint for the identifier such as user name, repository name or page name.
*/ */

View File

@@ -1,7 +1,6 @@
package gitbucket.core.controller package gitbucket.core.controller
import gitbucket.core.api._ import gitbucket.core.model.WebHook
import gitbucket.core.model.{Account, CommitStatus, CommitState, Repository, PullRequest, Issue, WebHook}
import gitbucket.core.pulls.html import gitbucket.core.pulls.html
import gitbucket.core.service.CommitStatusService import gitbucket.core.service.CommitStatusService
import gitbucket.core.service.MergeService import gitbucket.core.service.MergeService
@@ -82,24 +81,6 @@ trait PullRequestsControllerBase extends ControllerBase {
} }
}) })
/**
* https://developer.github.com/v3/pulls/#list-pull-requests
*/
get("/api/v3/repos/:owner/:repository/pulls")(referrersOnly { repository =>
val page = IssueSearchCondition.page(request)
// TODO: more api spec condition
val condition = IssueSearchCondition(request)
val baseOwner = getAccountByUserName(repository.owner).get
val issues:List[(Issue, Account, Int, PullRequest, Repository, Account)] = searchPullRequestByApi(condition, (page - 1) * PullRequestLimit, PullRequestLimit, repository.owner -> repository.name)
JsonFormat(issues.map{case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) =>
ApiPullRequest(
issue,
pullRequest,
ApiRepository(headRepo, ApiUser(headOwner)),
ApiRepository(repository, ApiUser(baseOwner)),
ApiUser(issueUser)) })
})
get("/:owner/:repository/pull/:id")(referrersOnly { repository => get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
params("id").toIntOpt.flatMap{ issueId => params("id").toIntOpt.flatMap{ issueId =>
val owner = repository.owner val owner = repository.owner
@@ -113,7 +94,7 @@ trait PullRequestsControllerBase extends ControllerBase {
(commits.flatten.map(commit => getCommitComments(owner, name, commit.id, true)).flatten.toList ::: getComments(owner, name, issueId)) (commits.flatten.map(commit => getCommitComments(owner, name, commit.id, true)).flatten.toList ::: getComments(owner, name, issueId))
.sortWith((a, b) => a.registeredDate before b.registeredDate), .sortWith((a, b) => a.registeredDate before b.registeredDate),
getIssueLabels(owner, name, issueId), getIssueLabels(owner, name, issueId),
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted, (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.groupAccount) Nil else List(owner))).sorted,
getMilestonesWithIssueCount(owner, name), getMilestonesWithIssueCount(owner, name),
getLabels(owner, name), getLabels(owner, name),
commits, commits,
@@ -126,47 +107,6 @@ trait PullRequestsControllerBase extends ControllerBase {
} getOrElse NotFound } getOrElse NotFound
}) })
/**
* https://developer.github.com/v3/pulls/#get-a-single-pull-request
*/
get("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository =>
(for{
issueId <- params("id").toIntOpt
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set())
baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName)
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
} yield {
JsonFormat(ApiPullRequest(
issue,
pullRequest,
ApiRepository(headRepo, ApiUser(headOwner)),
ApiRepository(repository, ApiUser(baseOwner)),
ApiUser(issueUser)))
}).getOrElse(NotFound)
})
/**
* https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request
*/
get("/api/v3/repos/:owner/:repository/pulls/:id/commits")(referrersOnly { repository =>
val owner = repository.owner
val name = repository.name
params("id").toIntOpt.flatMap{ issueId =>
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
using(Git.open(getRepositoryDir(owner, name))){ git =>
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
val newId = git.getRepository.resolve(pullreq.commitIdTo)
val repoFullName = RepositoryName(repository)
val commits = git.log.addRange(oldId, newId).call.iterator.asScala.map(c => ApiCommitListItem(new CommitInfo(c), repoFullName)).toList
JsonFormat(commits)
}
}
} getOrElse NotFound
})
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository => ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository =>
params("id").toIntOpt.flatMap{ issueId => params("id").toIntOpt.flatMap{ issueId =>
val owner = repository.owner val owner = repository.owner
@@ -430,7 +370,7 @@ trait PullRequestsControllerBase extends ControllerBase {
originRepository, originRepository,
forkedRepository, forkedRepository,
hasWritePermission(originRepository.owner, originRepository.name, context.loginAccount), hasWritePermission(originRepository.owner, originRepository.name, context.loginAccount),
(getCollaborators(originRepository.owner, originRepository.name) ::: (if(getAccountByUserName(originRepository.owner).get.isGroupAccount) Nil else List(originRepository.owner))).sorted, (getCollaborators(originRepository.owner, originRepository.name) ::: (if(getAccountByUserName(originRepository.owner).get.groupAccount) Nil else List(originRepository.owner))).sorted,
getMilestones(originRepository.owner, originRepository.name), getMilestones(originRepository.owner, originRepository.name),
getLabels(originRepository.owner, originRepository.name) getLabels(originRepository.owner, originRepository.name)
) )
@@ -523,7 +463,7 @@ trait PullRequestsControllerBase extends ControllerBase {
getIssue(owner, name, issueId.toString) foreach { issue => getIssue(owner, name, issueId.toString) foreach { issue =>
// extract references and create refer comment // extract references and create refer comment
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse("")) createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get)
// notifications // notifications
Notifier().toNotify(repository, issue, form.content.getOrElse("")){ Notifier().toNotify(repository, issue, form.content.getOrElse("")){
@@ -535,19 +475,6 @@ trait PullRequestsControllerBase extends ControllerBase {
} }
}) })
// TODO Same method exists in IssueController. Should it moved to IssueService?
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
StringUtil.extractIssueId(message).foreach { issueId =>
val content = fromIssue.issueId + ":" + fromIssue.title
if(getIssue(owner, repository, issueId).isDefined){
// Not add if refer comment already exist.
if(!getComments(owner, repository, issueId.toInt).exists { x => x.action == "refer" && x.content == content }) {
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt, content, "refer")
}
}
}
}
/** /**
* Parses branch identifier and extracts owner and branch name as tuple. * Parses branch identifier and extracts owner and branch name as tuple.
* *
@@ -597,7 +524,7 @@ trait PullRequestsControllerBase extends ControllerBase {
"pulls", "pulls",
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName), searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
page, page,
if(!getAccountByUserName(owner).exists(_.isGroupAccount)){ if(!getAccountByUserName(owner).exists(_.groupAccount)){
(getCollaborators(owner, repoName) :+ owner).sorted (getCollaborators(owner, repoName) :+ owner).sorted
} else { } else {
getCollaborators(owner, repoName) getCollaborators(owner, repoName)
@@ -611,14 +538,4 @@ trait PullRequestsControllerBase extends ControllerBase {
hasWritePermission(owner, repoName, context.loginAccount)) hasWritePermission(owner, repoName, context.loginAccount))
} }
// TODO: same as gitbucket.core.servlet.CommitLogHook ...
private def createIssueComment(owner: String, repository: String, 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")
}
}
}
}
} }

View File

@@ -49,11 +49,12 @@ trait RepositorySettingsControllerBase extends ControllerBase {
)(CollaboratorForm.apply) )(CollaboratorForm.apply)
// for web hook url addition // for web hook url addition
case class WebHookForm(url: String, events: Set[WebHook.Event]) case class WebHookForm(url: String, events: Set[WebHook.Event], token: Option[String])
def webHookForm(update:Boolean) = mapping( def webHookForm(update:Boolean) = mapping(
"url" -> trim(label("url", text(required, webHook(update)))), "url" -> trim(label("url", text(required, webHook(update)))),
"events" -> webhookEvents "events" -> webhookEvents,
"token" -> optional(trim(label("token", text(maxlength(100)))))
)(WebHookForm.apply) )(WebHookForm.apply)
// for transfer ownership // for transfer ownership
@@ -86,7 +87,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
repository.name, repository.name,
form.description, form.description,
repository.repository.parentUserName.map { _ => repository.repository.parentUserName.map { _ =>
repository.repository.isPrivate repository.repository.`private`
} getOrElse form.isPrivate } getOrElse form.isPrivate
) )
// Change repository name // Change repository name
@@ -141,29 +142,13 @@ trait RepositorySettingsControllerBase extends ControllerBase {
} }
}) })
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
import gitbucket.core.api._
(for{
branch <- params.get("branch") if repository.branchList.find(_ == branch).isDefined
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
} yield {
if(protection.enabled){
enableBranchProtection(repository.owner, repository.name, branch, protection.status.enforcement_level == ApiBranchProtection.Everyone, protection.status.contexts)
} else {
disableBranchProtection(repository.owner, repository.name, branch)
}
JsonFormat(ApiBranch(branch, protection)(RepositoryName(repository)))
}) getOrElse NotFound
})
/** /**
* Display the Collaborators page. * Display the Collaborators page.
*/ */
get("/:owner/:repository/settings/collaborators")(ownerOnly { repository => get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
html.collaborators( html.collaborators(
getCollaborators(repository.owner, repository.name), getCollaborators(repository.owner, repository.name),
getAccountByUserName(repository.owner).get.isGroupAccount, getAccountByUserName(repository.owner).get.groupAccount,
repository) repository)
}) })
@@ -171,7 +156,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Add the collaborator. * Add the collaborator.
*/ */
post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) =>
if(!getAccountByUserName(repository.owner).get.isGroupAccount){ if(!getAccountByUserName(repository.owner).get.groupAccount){
addCollaborator(repository.owner, repository.name, form.userName) addCollaborator(repository.owner, repository.name, form.userName)
} }
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
@@ -181,7 +166,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Add the collaborator. * Add the collaborator.
*/ */
get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository => get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository =>
if(!getAccountByUserName(repository.owner).get.isGroupAccount){ if(!getAccountByUserName(repository.owner).get.groupAccount){
removeCollaborator(repository.owner, repository.name, params("name")) removeCollaborator(repository.owner, repository.name, params("name"))
} }
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
@@ -198,7 +183,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Display the web hook edit page. * Display the web hook edit page.
*/ */
get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository => get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository =>
val webhook = WebHook(repository.owner, repository.name, "") val webhook = WebHook(repository.owner, repository.name, "", None)
html.edithooks(webhook, Set(WebHook.Push), repository, flash.get("info"), true) html.edithooks(webhook, Set(WebHook.Push), repository, flash.get("info"), true)
}) })
@@ -206,7 +191,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Add the web hook URL. * Add the web hook URL.
*/ */
post("/:owner/:repository/settings/hooks/new", webHookForm(false))(ownerOnly { (form, repository) => post("/:owner/:repository/settings/hooks/new", webHookForm(false))(ownerOnly { (form, repository) =>
addWebHook(repository.owner, repository.name, form.url, form.events) addWebHook(repository.owner, repository.name, form.url, form.events, form.token)
flash += "info" -> s"Webhook ${form.url} created" flash += "info" -> s"Webhook ${form.url} created"
redirect(s"/${repository.owner}/${repository.name}/settings/hooks") redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
}) })
@@ -235,7 +220,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
val url = params("url") val url = params("url")
val dummyWebHookInfo = WebHook(repository.owner, repository.name, url) val token = Some(params("token"))
val dummyWebHookInfo = WebHook(repository.owner, repository.name, url, token)
val dummyPayload = { val dummyPayload = {
val ownerAccount = getAccountByUserName(repository.owner).get val ownerAccount = getAccountByUserName(repository.owner).get
val commits = if(repository.commitCount == 0) List.empty else git.log val commits = if(repository.commitCount == 0) List.empty else git.log
@@ -294,7 +280,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Update web hook settings. * Update web hook settings.
*/ */
post("/:owner/:repository/settings/hooks/edit", webHookForm(true))(ownerOnly { (form, repository) => post("/:owner/:repository/settings/hooks/edit", webHookForm(true))(ownerOnly { (form, repository) =>
updateWebHook(repository.owner, repository.name, form.url, form.events) updateWebHook(repository.owner, repository.name, form.url, form.events, form.token)
flash += "info" -> s"webhook ${form.url} updated" flash += "info" -> s"webhook ${form.url} updated"
redirect(s"/${repository.owner}/${repository.name}/settings/hooks") redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
}) })
@@ -378,7 +364,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
override def validate(name: String, value: String, messages: Messages): Option[String] = override def validate(name: String, value: String, messages: Messages): Option[String] =
getAccountByUserName(value) match { getAccountByUserName(value) match {
case None => Some("User does not exist.") case None => Some("User does not exist.")
case Some(x) if(x.isGroupAccount) case Some(x) if(x.groupAccount)
=> Some("User does not exist.") => Some("User does not exist.")
case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName)) case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
=> Some("User can access this repository already.") => Some("User can access this repository already.")

View File

@@ -2,7 +2,6 @@ package gitbucket.core.controller
import javax.servlet.http.{HttpServletResponse, HttpServletRequest} import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
import gitbucket.core.api._
import gitbucket.core.plugin.PluginRegistry import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.repo.html import gitbucket.core.repo.html
import gitbucket.core.helper import gitbucket.core.helper
@@ -13,7 +12,7 @@ import gitbucket.core.util.StringUtil._
import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.model.{Account, CommitState, WebHook} import gitbucket.core.model.{Account, WebHook}
import gitbucket.core.service.WebHookService._ import gitbucket.core.service.WebHookService._
import gitbucket.core.view import gitbucket.core.view
import gitbucket.core.view.helpers import gitbucket.core.view.helpers
@@ -122,13 +121,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
fileList(_) fileList(_)
}) })
/**
* https://developer.github.com/v3/repos/#get
*/
get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
})
/** /**
* Displays the file list of the specified path and branch. * Displays the file list of the specified path and branch.
*/ */
@@ -160,65 +152,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
} }
}) })
/**
* https://developer.github.com/v3/repos/statuses/#create-a-status
*/
post("/api/v3/repos/:owner/:repo/statuses/:sha")(collaboratorsOnly { repository =>
(for{
ref <- params.get("sha")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
data <- extractFromJsonBody[CreateAStatus] if data.isValid
creator <- context.loginAccount
state <- CommitState.valueOf(data.state)
statusId = createCommitStatus(repository.owner, repository.name, sha, data.context.getOrElse("default"),
state, data.target_url, data.description, new java.util.Date(), creator)
status <- getCommitStatus(repository.owner, repository.name, statusId)
} yield {
JsonFormat(ApiCommitStatus(status, ApiUser(creator)))
}) getOrElse NotFound
})
/**
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
*
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
*/
val listStatusesRoute = get("/api/v3/repos/:owner/:repo/commits/:ref/statuses")(referrersOnly { repository =>
(for{
ref <- params.get("ref")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield {
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map{ case(status, creator) =>
ApiCommitStatus(status, ApiUser(creator))
})
}) getOrElse NotFound
})
/**
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
*
* legacy route
*/
get("/api/v3/repos/:owner/:repo/statuses/:ref"){
listStatusesRoute.action()
}
/**
* https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
*
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
*/
get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository =>
(for{
ref <- params.get("ref")
owner <- getAccountByUserName(repository.owner)
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield {
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
}) getOrElse NotFound
})
get("/:owner/:repository/new/*")(collaboratorsOnly { repository => get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
val (branch, path) = splitPath(repository, multiParams("splat").head) val (branch, path) = splitPath(repository, multiParams("splat").head)
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName) val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)

View File

@@ -1,18 +1,24 @@
package gitbucket.core.controller package gitbucket.core.controller
import gitbucket.core.admin.html import gitbucket.core.admin.html
import gitbucket.core.service.{AccountService, SystemSettingsService} import gitbucket.core.service.{AccountService, SystemSettingsService, RepositoryService}
import gitbucket.core.util.AdminAuthenticator import gitbucket.core.util.AdminAuthenticator
import gitbucket.core.ssh.SshServer import gitbucket.core.ssh.SshServer
import gitbucket.core.plugin.PluginRegistry import gitbucket.core.plugin.PluginRegistry
import SystemSettingsService._ import SystemSettingsService._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._
import gitbucket.core.util.StringUtil._
import io.github.gitbucket.scalatra.forms._ import io.github.gitbucket.scalatra.forms._
import org.apache.commons.io.FileUtils
import org.scalatra.i18n.Messages
class SystemSettingsController extends SystemSettingsControllerBase class SystemSettingsController extends SystemSettingsControllerBase
with AccountService with AdminAuthenticator with AccountService with RepositoryService with AdminAuthenticator
trait SystemSettingsControllerBase extends ControllerBase { trait SystemSettingsControllerBase extends AccountManagementControllerBase {
self: AccountService with AdminAuthenticator => self: AccountService with RepositoryService with AdminAuthenticator =>
private val form = mapping( private val form = mapping(
"baseUrl" -> trim(label("Base URL", optional(text()))), "baseUrl" -> trim(label("Base URL", optional(text()))),
@@ -68,6 +74,61 @@ trait SystemSettingsControllerBase extends ControllerBase {
case class PluginForm(pluginIds: List[String]) case class PluginForm(pluginIds: List[String])
case class NewUserForm(userName: String, password: String, fullName: String,
mailAddress: String, isAdmin: Boolean,
url: Option[String], fileId: Option[String])
case class EditUserForm(userName: String, password: Option[String], fullName: String,
mailAddress: String, isAdmin: Boolean, url: Option[String],
fileId: Option[String], clearImage: Boolean, isRemoved: Boolean)
case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String],
members: String)
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String],
members: String, clearImage: Boolean, isRemoved: Boolean)
val newUserForm = mapping(
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier, uniqueUserName))),
"password" -> trim(label("Password" ,text(required, maxlength(20)))),
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress()))),
"isAdmin" -> trim(label("User Type" ,boolean())),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text())))
)(NewUserForm.apply)
val editUserForm = mapping(
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier))),
"password" -> trim(label("Password" ,optional(text(maxlength(20))))),
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress("userName")))),
"isAdmin" -> trim(label("User Type" ,boolean())),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"clearImage" -> trim(label("Clear image" ,boolean())),
"removed" -> trim(label("Disable" ,boolean(disableByNotYourself("userName"))))
)(EditUserForm.apply)
val newGroupForm = mapping(
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"members" -> trim(label("Members" ,text(required, members)))
)(NewGroupForm.apply)
val editGroupForm = mapping(
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"members" -> trim(label("Members" ,text(required, members))),
"clearImage" -> trim(label("Clear image" ,boolean())),
"removed" -> trim(label("Disable" ,boolean()))
)(EditGroupForm.apply)
get("/admin/system")(adminOnly { get("/admin/system")(adminOnly {
html.system(flash.get("info")) html.system(flash.get("info"))
}) })
@@ -92,4 +153,138 @@ trait SystemSettingsControllerBase extends ControllerBase {
html.plugins(PluginRegistry().getPlugins()) html.plugins(PluginRegistry().getPlugins())
}) })
get("/admin/users")(adminOnly {
val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false)
val users = getAllUsers(includeRemoved)
val members = users.collect { case account if(account.groupAccount) =>
account.userName -> getGroupMembers(account.userName).map(_.userName)
}.toMap
html.userlist(users, members, includeRemoved)
})
get("/admin/users/_newuser")(adminOnly {
html.user(None)
})
post("/admin/users/_newuser", newUserForm)(adminOnly { form =>
createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, form.isAdmin, form.url)
updateImage(form.userName, form.fileId, false)
redirect("/admin/users")
})
get("/admin/users/:userName/_edituser")(adminOnly {
val userName = params("userName")
html.user(getAccountByUserName(userName, true))
})
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
val userName = params("userName")
getAccountByUserName(userName, true).map { account =>
if(form.isRemoved){
// Remove repositories
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
// deleteRepository(userName, repositoryName)
// FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
// }
// Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
removeUserRelatedData(userName)
}
updateAccount(account.copy(
password = form.password.map(sha1).getOrElse(account.password),
fullName = form.fullName,
mailAddress = form.mailAddress,
administrator = form.isAdmin,
url = form.url,
removed = form.isRemoved))
updateImage(userName, form.fileId, form.clearImage)
redirect("/admin/users")
} getOrElse NotFound
})
get("/admin/users/_newgroup")(adminOnly {
html.usergroup(None, Nil)
})
post("/admin/users/_newgroup", newGroupForm)(adminOnly { form =>
createGroup(form.groupName, form.url)
updateGroupMembers(form.groupName, form.members.split(",").map {
_.split(":") match {
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
}.toList)
updateImage(form.groupName, form.fileId, false)
redirect("/admin/users")
})
get("/admin/users/:groupName/_editgroup")(adminOnly {
defining(params("groupName")){ groupName =>
html.usergroup(getAccountByUserName(groupName, true), getGroupMembers(groupName))
}
})
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
defining(params("groupName"), form.members.split(",").map {
_.split(":") match {
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
}.toList){ case (groupName, members) =>
getAccountByUserName(groupName, true).map { account =>
updateGroup(groupName, form.url, form.isRemoved)
if(form.isRemoved){
// Remove from GROUP_MEMBER
updateGroupMembers(form.groupName, Nil)
// Remove repositories
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
deleteRepository(groupName, repositoryName)
FileUtils.deleteDirectory(getRepositoryDir(groupName, repositoryName))
FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
}
} else {
// Update GROUP_MEMBER
updateGroupMembers(form.groupName, members)
// Update COLLABORATOR for group repositories
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
removeCollaborators(form.groupName, repositoryName)
members.foreach { case (userName, isManager) =>
addCollaborator(form.groupName, repositoryName, userName)
}
}
}
updateImage(form.groupName, form.fileId, form.clearImage)
redirect("/admin/users")
} getOrElse NotFound
}
})
private def members: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] = {
if(value.split(",").exists {
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
}) None else Some("Must select one manager at least.")
}
}
protected def disableByNotYourself(paramName: String): Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
params.get(paramName).flatMap { userName =>
if(userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
Some("You can't disable your account yourself")
else
None
}
}
}
} }

View File

@@ -1,204 +0,0 @@
package gitbucket.core.controller
import gitbucket.core.service.{RepositoryService, AccountService}
import gitbucket.core.admin.users.html
import gitbucket.core.util._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.StringUtil._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._
import io.github.gitbucket.scalatra.forms._
import org.scalatra.i18n.Messages
import org.apache.commons.io.FileUtils
class UserManagementController extends UserManagementControllerBase
with AccountService with RepositoryService with AdminAuthenticator
trait UserManagementControllerBase extends AccountManagementControllerBase {
self: AccountService with RepositoryService with AdminAuthenticator =>
case class NewUserForm(userName: String, password: String, fullName: String,
mailAddress: String, isAdmin: Boolean,
url: Option[String], fileId: Option[String])
case class EditUserForm(userName: String, password: Option[String], fullName: String,
mailAddress: String, isAdmin: Boolean, url: Option[String],
fileId: Option[String], clearImage: Boolean, isRemoved: Boolean)
case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String],
members: String)
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String],
members: String, clearImage: Boolean, isRemoved: Boolean)
val newUserForm = mapping(
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier, uniqueUserName))),
"password" -> trim(label("Password" ,text(required, maxlength(20)))),
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress()))),
"isAdmin" -> trim(label("User Type" ,boolean())),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text())))
)(NewUserForm.apply)
val editUserForm = mapping(
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier))),
"password" -> trim(label("Password" ,optional(text(maxlength(20))))),
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress("userName")))),
"isAdmin" -> trim(label("User Type" ,boolean())),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"clearImage" -> trim(label("Clear image" ,boolean())),
"removed" -> trim(label("Disable" ,boolean(disableByNotYourself("userName"))))
)(EditUserForm.apply)
val newGroupForm = mapping(
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"members" -> trim(label("Members" ,text(required, members)))
)(NewGroupForm.apply)
val editGroupForm = mapping(
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"members" -> trim(label("Members" ,text(required, members))),
"clearImage" -> trim(label("Clear image" ,boolean())),
"removed" -> trim(label("Disable" ,boolean()))
)(EditGroupForm.apply)
get("/admin/users")(adminOnly {
val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false)
val users = getAllUsers(includeRemoved)
val members = users.collect { case account if(account.isGroupAccount) =>
account.userName -> getGroupMembers(account.userName).map(_.userName)
}.toMap
html.list(users, members, includeRemoved)
})
get("/admin/users/_newuser")(adminOnly {
html.user(None)
})
post("/admin/users/_newuser", newUserForm)(adminOnly { form =>
createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, form.isAdmin, form.url)
updateImage(form.userName, form.fileId, false)
redirect("/admin/users")
})
get("/admin/users/:userName/_edituser")(adminOnly {
val userName = params("userName")
html.user(getAccountByUserName(userName, true))
})
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
val userName = params("userName")
getAccountByUserName(userName, true).map { account =>
if(form.isRemoved){
// Remove repositories
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
// deleteRepository(userName, repositoryName)
// FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
// }
// Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
removeUserRelatedData(userName)
}
updateAccount(account.copy(
password = form.password.map(sha1).getOrElse(account.password),
fullName = form.fullName,
mailAddress = form.mailAddress,
isAdmin = form.isAdmin,
url = form.url,
isRemoved = form.isRemoved))
updateImage(userName, form.fileId, form.clearImage)
redirect("/admin/users")
} getOrElse NotFound
})
get("/admin/users/_newgroup")(adminOnly {
html.group(None, Nil)
})
post("/admin/users/_newgroup", newGroupForm)(adminOnly { form =>
createGroup(form.groupName, form.url)
updateGroupMembers(form.groupName, form.members.split(",").map {
_.split(":") match {
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
}.toList)
updateImage(form.groupName, form.fileId, false)
redirect("/admin/users")
})
get("/admin/users/:groupName/_editgroup")(adminOnly {
defining(params("groupName")){ groupName =>
html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
}
})
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
defining(params("groupName"), form.members.split(",").map {
_.split(":") match {
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
}.toList){ case (groupName, members) =>
getAccountByUserName(groupName, true).map { account =>
updateGroup(groupName, form.url, form.isRemoved)
if(form.isRemoved){
// Remove from GROUP_MEMBER
updateGroupMembers(form.groupName, Nil)
// Remove repositories
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
deleteRepository(groupName, repositoryName)
FileUtils.deleteDirectory(getRepositoryDir(groupName, repositoryName))
FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
}
} else {
// Update GROUP_MEMBER
updateGroupMembers(form.groupName, members)
// Update COLLABORATOR for group repositories
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
removeCollaborators(form.groupName, repositoryName)
members.foreach { case (userName, isManager) =>
addCollaborator(form.groupName, repositoryName, userName)
}
}
}
updateImage(form.groupName, form.fileId, form.clearImage)
redirect("/admin/users")
} getOrElse NotFound
}
})
private def members: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] = {
if(value.split(",").exists {
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
}) None else Some("Must select one manager at least.")
}
}
protected def disableByNotYourself(paramName: String): Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
params.get(paramName).flatMap { userName =>
if(userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
Some("You can't disable your account yourself")
else
None
}
}
}
}

View File

@@ -11,7 +11,7 @@ trait AccountComponent { self: Profile =>
val fullName = column[String]("FULL_NAME") val fullName = column[String]("FULL_NAME")
val mailAddress = column[String]("MAIL_ADDRESS") val mailAddress = column[String]("MAIL_ADDRESS")
val password = column[String]("PASSWORD") val password = column[String]("PASSWORD")
val isAdmin = column[Boolean]("ADMINISTRATOR") val administrator = column[Boolean]("ADMINISTRATOR")
val url = column[String]("URL") val url = column[String]("URL")
val registeredDate = column[java.util.Date]("REGISTERED_DATE") val registeredDate = column[java.util.Date]("REGISTERED_DATE")
val updatedDate = column[java.util.Date]("UPDATED_DATE") val updatedDate = column[java.util.Date]("UPDATED_DATE")
@@ -19,7 +19,7 @@ trait AccountComponent { self: Profile =>
val image = column[String]("IMAGE") val image = column[String]("IMAGE")
val groupAccount = column[Boolean]("GROUP_ACCOUNT") val groupAccount = column[Boolean]("GROUP_ACCOUNT")
val removed = column[Boolean]("REMOVED") val removed = column[Boolean]("REMOVED")
def * = (userName, fullName, mailAddress, password, isAdmin, url.?, registeredDate, updatedDate, lastLoginDate.?, image.?, groupAccount, removed) <> (Account.tupled, Account.unapply) def * = (userName, fullName, mailAddress, password, administrator, url.?, registeredDate, updatedDate, lastLoginDate.?, image.?, groupAccount, removed) <> (Account.tupled, Account.unapply)
} }
} }
@@ -28,12 +28,12 @@ case class Account(
fullName: String, fullName: String,
mailAddress: String, mailAddress: String,
password: String, password: String,
isAdmin: Boolean, administrator: Boolean,
url: Option[String], url: Option[String],
registeredDate: java.util.Date, registeredDate: java.util.Date,
updatedDate: java.util.Date, updatedDate: java.util.Date,
lastLoginDate: Option[java.util.Date], lastLoginDate: Option[java.util.Date],
image: Option[String], image: Option[String],
isGroupAccount: Boolean, groupAccount: Boolean,
isRemoved: Boolean removed: Boolean
) )

View File

@@ -8,13 +8,13 @@ trait GroupMemberComponent { self: Profile =>
class GroupMembers(tag: Tag) extends Table[GroupMember](tag, "GROUP_MEMBER") { class GroupMembers(tag: Tag) extends Table[GroupMember](tag, "GROUP_MEMBER") {
val groupName = column[String]("GROUP_NAME", O PrimaryKey) val groupName = column[String]("GROUP_NAME", O PrimaryKey)
val userName = column[String]("USER_NAME", O PrimaryKey) val userName = column[String]("USER_NAME", O PrimaryKey)
val isManager = column[Boolean]("MANAGER") val manager = column[Boolean]("MANAGER")
def * = (groupName, userName, isManager) <> (GroupMember.tupled, GroupMember.unapply) def * = (groupName, userName, manager) <> (GroupMember.tupled, GroupMember.unapply)
} }
} }
case class GroupMember( case class GroupMember(
groupName: String, groupName: String,
userName: String, userName: String,
isManager: Boolean manager: Boolean
) )

View File

@@ -26,7 +26,7 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
case class Repository( case class Repository(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
isPrivate: Boolean, `private`: Boolean,
description: Option[String], description: Option[String],
defaultBranch: String, defaultBranch: String,
registeredDate: java.util.Date, registeredDate: java.util.Date,

View File

@@ -7,7 +7,8 @@ trait WebHookComponent extends TemplateComponent { self: Profile =>
class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate { class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate {
val url = column[String]("URL") val url = column[String]("URL")
def * = (userName, repositoryName, url) <> ((WebHook.apply _).tupled, WebHook.unapply) val token = column[Option[String]]("TOKEN", O.Nullable)
def * = (userName, repositoryName, url, token) <> ((WebHook.apply _).tupled, WebHook.unapply)
def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind) def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
} }
@@ -16,7 +17,8 @@ trait WebHookComponent extends TemplateComponent { self: Profile =>
case class WebHook( case class WebHook(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
url: String url: String,
token: Option[String]
) )
object WebHook { object WebHook {

View File

@@ -5,6 +5,8 @@ import profile.simple._
import gitbucket.core.model.{Account, AccessToken} import gitbucket.core.model.{Account, AccessToken}
import gitbucket.core.util.StringUtil import gitbucket.core.util.StringUtil
import gitbucket.core.servlet.Database._
import io.getquill._
import scala.util.Random import scala.util.Random
@@ -27,28 +29,36 @@ trait AccessTokenService {
var hash: String = null var hash: String = null
do{ do{
token = makeAccessTokenString token = makeAccessTokenString
hash = tokenToHash(token) hash = tokenToHash(token)
}while(AccessTokens.filter(_.tokenHash === hash.bind).exists.run) } while (
db.run(quote { (hash: String) => query[AccessToken].filter(_.tokenHash == hash).nonEmpty })(hash).head
)
val newToken = AccessToken( val newToken = AccessToken(
userName = userName, userName = userName,
note = note, note = note,
tokenHash = hash) tokenHash = hash)
// TODO Remain Slick code
val tokenId = (AccessTokens returning AccessTokens.map(_.accessTokenId)) += newToken val tokenId = (AccessTokens returning AccessTokens.map(_.accessTokenId)) += newToken
(tokenId, token) (tokenId, token)
} }
def getAccountByAccessToken(token: String)(implicit s: Session): Option[Account] = def getAccountByAccessToken(token: String): Option[Account] =
Accounts db.run(quote { (tokenHash: String) =>
.innerJoin(AccessTokens) query[AccessToken].filter(_.tokenHash == tokenHash)
.filter{ case (ac, t) => (ac.userName === t.userName) && (t.tokenHash === tokenToHash(token).bind) && (ac.removed === false.bind) } .join(query[Account]).on { (t, a) => t.userName == a.userName && a.registeredDate == false }
.map{ case (ac, t) => ac } .map { case (t, a) => a }
.firstOption })(tokenToHash(token)).headOption
def getAccessTokens(userName: String)(implicit s: Session): List[AccessToken] = def getAccessTokens(userName: String): List[AccessToken] =
AccessTokens.filter(_.userName === userName.bind).sortBy(_.accessTokenId.desc).list db.run(quote { (userName: String) =>
query[AccessToken].filter(_.userName == userName).sortBy(_.accessTokenId)(Ord.desc)
})(userName)
def deleteAccessToken(userName: String, accessTokenId: Int)(implicit s: Session): Unit = def deleteAccessToken(userName: String, accessTokenId: Int): Unit =
AccessTokens filter (t => t.userName === userName.bind && t.accessTokenId === accessTokenId) delete db.run(quote { (userName: String, accessTokenId: Int) =>
query[AccessToken].filter { t => t.userName == userName && t.accessTokenId == accessTokenId }.delete
})(List((userName, accessTokenId)))
} }

View File

@@ -1,20 +1,23 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.model.{GroupMember, Account} import java.util.Date
import gitbucket.core.model.{GroupMember, Account, Collaborator, Repository}
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import gitbucket.core.util.{StringUtil, LDAPUtil} import gitbucket.core.util.{StringUtil, LDAPUtil}
import gitbucket.core.service.SystemSettingsService.SystemSettings import gitbucket.core.service.SystemSettingsService.SystemSettings
import profile.simple._ import profile.simple._
import StringUtil._ import StringUtil._
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
// TODO Why is direct import required?
import gitbucket.core.model.Profile.dateColumnType import gitbucket.core.servlet.Database._
import io.getquill._
trait AccountService { trait AccountService {
private val logger = LoggerFactory.getLogger(classOf[AccountService]) private val logger = LoggerFactory.getLogger(classOf[AccountService])
def authenticate(settings: SystemSettings, userName: String, password: String)(implicit s: Session): Option[Account] = def authenticate(settings: SystemSettings, userName: String, password: String): Option[Account] =
if(settings.ldapAuthentication){ if(settings.ldapAuthentication){
ldapAuthentication(settings, userName, password) ldapAuthentication(settings, userName, password)
} else { } else {
@@ -24,22 +27,21 @@ trait AccountService {
/** /**
* Authenticate by internal database. * Authenticate by internal database.
*/ */
private def defaultAuthentication(userName: String, password: String)(implicit s: Session) = { private def defaultAuthentication(userName: String, password: String) = {
getAccountByUserName(userName).collect { getAccountByUserName(userName).collect {
case account if(!account.isGroupAccount && account.password == sha1(password)) => Some(account) case account if(!account.groupAccount && account.password == sha1(password)) => Some(account)
} getOrElse None } getOrElse None
} }
/** /**
* Authenticate by LDAP. * Authenticate by LDAP.
*/ */
private def ldapAuthentication(settings: SystemSettings, userName: String, password: String) private def ldapAuthentication(settings: SystemSettings, userName: String, password: String): Option[Account] = {
(implicit s: Session): Option[Account] = {
LDAPUtil.authenticate(settings.ldap.get, userName, password) match { LDAPUtil.authenticate(settings.ldap.get, userName, password) match {
case Right(ldapUserInfo) => { case Right(ldapUserInfo) => {
// Create or update account by LDAP information // Create or update account by LDAP information
getAccountByUserName(ldapUserInfo.userName, true) match { getAccountByUserName(ldapUserInfo.userName, true) match {
case Some(x) if(!x.isRemoved) => { case Some(x) if(!x.removed) => {
if(settings.ldap.get.mailAttribute.getOrElse("").isEmpty) { if(settings.ldap.get.mailAttribute.getOrElse("").isEmpty) {
updateAccount(x.copy(fullName = ldapUserInfo.fullName)) updateAccount(x.copy(fullName = ldapUserInfo.fullName))
} else { } else {
@@ -47,16 +49,16 @@ trait AccountService {
} }
getAccountByUserName(ldapUserInfo.userName) getAccountByUserName(ldapUserInfo.userName)
} }
case Some(x) if(x.isRemoved) => { case Some(x) if(x.removed) => {
logger.info("LDAP Authentication Failed: Account is already registered but disabled.") logger.info("LDAP Authentication Failed: Account is already registered but disabled.")
defaultAuthentication(userName, password) defaultAuthentication(userName, password)
} }
case None => getAccountByMailAddress(ldapUserInfo.mailAddress, true) match { case None => getAccountByMailAddress(ldapUserInfo.mailAddress, true) match {
case Some(x) if(!x.isRemoved) => { case Some(x) if(!x.removed) => {
updateAccount(x.copy(fullName = ldapUserInfo.fullName)) updateAccount(x.copy(fullName = ldapUserInfo.fullName))
getAccountByUserName(ldapUserInfo.userName) getAccountByUserName(ldapUserInfo.userName)
} }
case Some(x) if(x.isRemoved) => { case Some(x) if(x.removed) => {
logger.info("LDAP Authentication Failed: Account is already registered but disabled.") logger.info("LDAP Authentication Failed: Account is already registered but disabled.")
defaultAuthentication(userName, password) defaultAuthentication(userName, password)
} }
@@ -74,113 +76,163 @@ trait AccountService {
} }
} }
def getAccountByUserName(userName: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] = def getAccountByUserName(userName: String, includeRemoved: Boolean = false): Option[Account] = {
Accounts filter(t => (t.userName === userName.bind) && (t.removed === false.bind, !includeRemoved)) firstOption db.run(quote { (userName: String, includeRemoved: Boolean) =>
query[Account].filter { t =>
if(includeRemoved){
t.userName == userName
} else {
t.userName == userName && t.removed == false
}
}
})(userName, includeRemoved).headOption
}
def getAccountsByUserNames(userNames: Set[String], knowns:Set[Account], includeRemoved: Boolean = false)(implicit s: Session): Map[String, Account] = {
def getAccountsByUserNames(userNames: Set[String], knowns:Set[Account], includeRemoved: Boolean = false): Map[String, Account] = {
val map = knowns.map(a => a.userName -> a).toMap val map = knowns.map(a => a.userName -> a).toMap
val needs = userNames -- map.keySet val needs = userNames -- map.keySet
if(needs.isEmpty){ if(needs.isEmpty){
map map
}else{ } else {
map ++ Accounts.filter(t => (t.userName inSetBind needs) && (t.removed === false.bind, !includeRemoved)).list.map(a => a.userName -> a).toMap map ++ db.run(quote { (userNames: Set[String]) =>
query[Account].filter { t => userNames.contains(t.userName) && t.removed == false }
})(userNames.toSet).map { a => a.userName -> a }.toMap
} }
} }
def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] = def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false): Option[Account] = {
Accounts filter(t => (t.mailAddress.toLowerCase === mailAddress.toLowerCase.bind) && (t.removed === false.bind, !includeRemoved)) firstOption db.run(quote { (mailAddress: String, includeRemoved: Boolean) =>
query[Account].filter { t =>
if(includeRemoved){
t.mailAddress.toLowerCase == mailAddress.toLowerCase
} else {
t.mailAddress.toLowerCase == mailAddress.toLowerCase && t.removed == false
}
}
})(mailAddress, includeRemoved).headOption
}
def getAllUsers(includeRemoved: Boolean = true)(implicit s: Session): List[Account] = def getAllUsers(includeRemoved: Boolean = true): List[Account] = {
if(includeRemoved){ db.run(
Accounts sortBy(_.userName) list if(includeRemoved){
} else { quote { query[Account].sortBy(_.userName) }
Accounts filter (_.removed === false.bind) sortBy(_.userName) list } else {
} quote { query[Account].filter(_.removed == false).sortBy(_.userName) }
}
)
}
def createAccount(userName: String, password: String, fullName: String, mailAddress: String, isAdmin: Boolean, url: Option[String]) def createAccount(userName: String, password: String, fullName: String, mailAddress: String, isAdmin: Boolean, url: Option[String]): Unit = {
(implicit s: Session): Unit = db.run(quote { query[Account].insert })(Account(
Accounts insert Account(
userName = userName, userName = userName,
password = password, password = password,
fullName = fullName, fullName = fullName,
mailAddress = mailAddress, mailAddress = mailAddress,
isAdmin = isAdmin, administrator = isAdmin,
url = url, url = url,
registeredDate = currentDate, registeredDate = currentDate,
updatedDate = currentDate, updatedDate = currentDate,
lastLoginDate = None, lastLoginDate = None,
image = None, image = None,
isGroupAccount = false, groupAccount = false,
isRemoved = false) removed = false
))
}
def updateAccount(account: Account)(implicit s: Session): Unit = def updateAccount(account: Account): Unit = {
Accounts db.run(quote { (userName: String, password: String, fullName: String, mailAddress: String, administrator: Boolean,
.filter { a => a.userName === account.userName.bind } url: Option[String], registeredDate: Date, updatedDate: Date, lastLoginDate: Option[Date], removed: Boolean) =>
.map { a => (a.password, a.fullName, a.mailAddress, a.isAdmin, a.url.?, a.registeredDate, a.updatedDate, a.lastLoginDate.?, a.removed) } query[Account].filter(_.userName == userName).update(
.update ( _.password -> password,
account.password, _.fullName -> fullName,
account.fullName, _.mailAddress -> mailAddress,
account.mailAddress, _.administrator -> administrator,
account.isAdmin, _.url -> url,
account.url, _.registeredDate -> registeredDate,
account.registeredDate, _.updatedDate -> updatedDate,
currentDate, _.lastLoginDate -> lastLoginDate,
account.lastLoginDate, _.removed -> removed
account.isRemoved) )
})((
account.userName,
account.password,
account.fullName,
account.mailAddress,
account.administrator,
account.url,
account.registeredDate,
currentDate,
account.lastLoginDate,
account.removed
))
}
def updateAvatarImage(userName: String, image: Option[String])(implicit s: Session): Unit = def updateAvatarImage(userName: String, image: Option[String]): Unit = {
Accounts.filter(_.userName === userName.bind).map(_.image.?).update(image) db.run(quote { (userName: String, image: Option[String]) =>
query[Account].filter(_.userName == userName).update(_.image -> image)
})((userName, image))
}
def updateLastLoginDate(userName: String)(implicit s: Session): Unit = def updateLastLoginDate(userName: String): Unit = {
Accounts.filter(_.userName === userName.bind).map(_.lastLoginDate).update(currentDate) db.run(quote { (userName: String, lastLoginDate: Option[Date]) =>
query[Account].filter(_.userName == userName).update(_.lastLoginDate -> lastLoginDate)
})((userName, Some(currentDate)))
}
def createGroup(groupName: String, url: Option[String])(implicit s: Session): Unit = def createGroup(groupName: String, url: Option[String]): Unit = {
Accounts insert Account( db.run( quote { query[Account].insert })(List(Account(
userName = groupName, userName = groupName,
password = "", password = "",
fullName = groupName, fullName = groupName,
mailAddress = groupName + "@devnull", mailAddress = groupName + "@devnull",
isAdmin = false, administrator = false,
url = url, url = url,
registeredDate = currentDate, registeredDate = currentDate,
updatedDate = currentDate, updatedDate = currentDate,
lastLoginDate = None, lastLoginDate = None,
image = None, image = None,
isGroupAccount = true, groupAccount = true,
isRemoved = false) removed = false
)))
}
def updateGroup(groupName: String, url: Option[String], removed: Boolean)(implicit s: Session): Unit = def updateGroup(groupName: String, url: Option[String], removed: Boolean): Unit = {
Accounts.filter(_.userName === groupName.bind).map(t => t.url.? -> t.removed).update(url, removed) db.run(quote { (groupName: String, url: Option[String], removed: Boolean) =>
query[Account].filter(_.userName == groupName).update(_.url -> url, _.removed -> removed)
})(List((groupName, url, removed)))
}
def updateGroupMembers(groupName: String, members: List[(String, Boolean)]): Unit = {
db.run(quote { (groupName: String) => query[GroupMember].filter(_.groupName == groupName).delete })(groupName)
def updateGroupMembers(groupName: String, members: List[(String, Boolean)])(implicit s: Session): Unit = {
GroupMembers.filter(_.groupName === groupName.bind).delete
members.foreach { case (userName, isManager) => members.foreach { case (userName, isManager) =>
GroupMembers insert GroupMember (groupName, userName, isManager) db.run(quote { query[GroupMember].insert })(GroupMember(groupName, userName, isManager))
} }
} }
def getGroupMembers(groupName: String)(implicit s: Session): List[GroupMember] = def getGroupMembers(groupName: String): List[GroupMember] = {
GroupMembers db.run(quote { (groupName: String) =>
.filter(_.groupName === groupName.bind) query[GroupMember].filter(_.groupName == groupName).sortBy(_.userName)
.sortBy(_.userName) })(groupName)
.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] = { def getGroupsByUserName(userName: String): List[String] = {
List(userName) ++ db.run(quote { (userName: String) =>
Collaborators.filter(_.collaboratorName === userName.bind).sortBy(_.userName).map(_.userName).list query[GroupMember].filter(_.userName == userName).sortBy(_.groupName).map(_.groupName)
})(userName)
}
def removeUserRelatedData(userName: String): Unit = {
db.run(quote { (userName: String) => query[GroupMember].filter(_.userName == userName).delete })(userName)
db.run(quote { (userName: String) => query[Collaborator].filter(_.collaboratorName == userName).delete })(userName)
db.run(quote { (userName: String) => query[Repository].filter(_.userName == userName).delete })(userName)
}
def getGroupNames(userName: String): List[String] = {
List(userName) ++ db.run(quote { (userName: String) =>
query[Collaborator].filter(_.collaboratorName == userName).sortBy(_.userName).map(_.userName)
})(userName)
} }
} }

View File

@@ -1,194 +1,180 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.model.Activity import gitbucket.core.model._
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import gitbucket.core.util.JGitUtil import gitbucket.core.util.JGitUtil
import profile.simple._ import profile.simple._
import gitbucket.core.servlet.Database._
import io.getquill._
trait ActivityService { trait ActivityService {
def deleteOldActivities(limit: Int)(implicit s: Session): Int = { def deleteOldActivities(limit: Int): Int =
Activities.map(_.activityId).sortBy(_ desc).drop(limit).firstOption.map { id => db.run (quote { (limit: Int) =>
Activities.filter(_.activityId <= id.bind).delete query[Activity].map(_.activityId).sortBy(x => x)(Ord.desc).drop(limit)
})(limit).headOption.map { activityId =>
db.run (
quote { (activityId: Int) => query[Activity].filter(_.activityId <= activityId).delete }
)(activityId)
} getOrElse 0 } getOrElse 0
}
def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit s: Session): List[Activity] = def getActivitiesByUser(activityUserName: String, isPublic: Boolean): List[Activity] =
Activities db.run(quote { (activityUserName: String, isPublic: Boolean) =>
.innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) query[Activity].join(query[Repository]).on((a, r) => a.userName == r.userName && a.repositoryName == r.repositoryName)
.filter { case (t1, t2) => .filter { case (a, r) =>
if(isPublic){ if(isPublic){
(t1.activityUserName === activityUserName.bind) && (t2.isPrivate === false.bind) a.activityUserName == activityUserName
} else { } else {
(t1.activityUserName === activityUserName.bind) a.activityUserName == activityUserName && r.`private` == false
}
} }
} .sortBy { case (a, r) => a.activityId }(Ord.desc)
.sortBy { case (t1, t2) => t1.activityId desc } .map { case (a, r) => a }
.map { case (t1, t2) => t1 } .take(30)
.take(30) })(activityUserName, isPublic)
.list
def getRecentActivities()(implicit s: Session): List[Activity] = def getRecentActivities(): List[Activity] =
Activities db.run(quote {
.innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) query[Activity].join(query[Repository]).on((a, r) => a.userName == r.userName && a.repositoryName == r.repositoryName)
.filter { case (t1, t2) => t2.isPrivate === false.bind } .filter { case (a, r) => r.`private` == false}
.sortBy { case (t1, t2) => t1.activityId desc } .sortBy { case (a, r) => a.activityId }(Ord.desc)
.map { case (t1, t2) => t1 } .map { case (a, r) => a }
.take(30) .take(30)
.list })
def getRecentActivitiesByOwners(owners : Set[String])(implicit s: Session): List[Activity] = def getRecentActivitiesByOwners(owners : Set[String]): List[Activity] =
Activities db.run(quote { (owners: Set[String]) =>
.innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) query[Activity].join(query[Repository]).on((a, r) => a.userName == r.userName && a.repositoryName == r.repositoryName)
.filter { case (t1, t2) => (t2.isPrivate === false.bind) || (t2.userName inSetBind owners) } .filter { case (a, r) => r.`private` == false || owners.contains(r.userName) }
.sortBy { case (t1, t2) => t1.activityId desc } .sortBy { case (a, r) => a.activityId }(Ord.desc)
.map { case (t1, t2) => t1 } .map { case (a, r) => a }
.take(30) .take(30)
.list })(owners)
def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String) def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String): Unit =
(implicit s: Session): Unit = insertActivity(userName, repositoryName, activityUserName,
Activities insert Activity(userName, repositoryName, activityUserName,
"create_repository", "create_repository",
s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]", s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]",
None, None)
currentDate)
def recordCreateIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String) def recordCreateIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit =
(implicit s: Session): Unit = insertActivity(userName, repositoryName, activityUserName,
Activities insert Activity(userName, repositoryName, activityUserName,
"open_issue", "open_issue",
s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(title), Some(title))
currentDate)
def recordCloseIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String) def recordCloseIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit =
(implicit s: Session): Unit = insertActivity(userName, repositoryName, activityUserName,
Activities insert Activity(userName, repositoryName, activityUserName,
"close_issue", "close_issue",
s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(title), Some(title))
currentDate)
def recordClosePullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String) def recordClosePullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit =
(implicit s: Session): Unit = insertActivity(userName, repositoryName, activityUserName,
Activities insert Activity(userName, repositoryName, activityUserName,
"close_issue", "close_issue",
s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(title), Some(title))
currentDate)
def recordReopenIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String) def recordReopenIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit =
(implicit s: Session): Unit = insertActivity(userName, repositoryName, activityUserName,
Activities insert Activity(userName, repositoryName, activityUserName,
"reopen_issue", "reopen_issue",
s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(title), Some(title))
currentDate)
def recordCommentIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String) def recordCommentIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String): Unit =
(implicit s: Session): Unit = insertActivity(userName, repositoryName, activityUserName,
Activities insert Activity(userName, repositoryName, activityUserName,
"comment_issue", "comment_issue",
s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]",
Some(cut(comment, 200)), Some(cut(comment, 200)))
currentDate)
def recordCommentPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String) def recordCommentPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String): Unit =
(implicit s: Session): Unit = insertActivity(userName, repositoryName, activityUserName,
Activities insert Activity(userName, repositoryName, activityUserName,
"comment_issue", "comment_issue",
s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(cut(comment, 200)), Some(cut(comment, 200)))
currentDate)
def recordCommentCommitActivity(userName: String, repositoryName: String, activityUserName: String, commitId: String, comment: String) def recordCommentCommitActivity(userName: String, repositoryName: String, activityUserName: String, commitId: String, comment: String): Unit =
(implicit s: Session): Unit = insertActivity(userName, repositoryName, activityUserName,
Activities insert Activity(userName, repositoryName, activityUserName,
"comment_commit", "comment_commit",
s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]", s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]",
Some(cut(comment, 200)), Some(cut(comment, 200)))
currentDate
)
def recordCreateWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String) def recordCreateWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String): Unit =
(implicit s: Session): Unit = insertActivity(userName, repositoryName, activityUserName,
Activities insert Activity(userName, repositoryName, activityUserName,
"create_wiki", "create_wiki",
s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki", s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki",
Some(pageName), Some(pageName))
currentDate)
def recordEditWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String, commitId: String) def recordEditWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String, commitId: String): Unit =
(implicit s: Session): Unit = insertActivity(userName, repositoryName, activityUserName,
Activities insert Activity(userName, repositoryName, activityUserName,
"edit_wiki", "edit_wiki",
s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki", s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki",
Some(pageName + ":" + commitId), Some(pageName + ":" + commitId))
currentDate)
def recordPushActivity(userName: String, repositoryName: String, activityUserName: String, def recordPushActivity(userName: String, repositoryName: String, activityUserName: String,
branchName: String, commits: List[JGitUtil.CommitInfo])(implicit s: Session): Unit = branchName: String, commits: List[JGitUtil.CommitInfo]): Unit =
Activities insert Activity(userName, repositoryName, activityUserName, insertActivity(userName, repositoryName, activityUserName,
"push", "push",
s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
Some(commits.map { commit => commit.id + ":" + commit.shortMessage }.mkString("\n")), Some(commits.map { commit => commit.id + ":" + commit.shortMessage }.mkString("\n")))
currentDate)
def recordCreateTagActivity(userName: String, repositoryName: String, activityUserName: String, def recordCreateTagActivity(userName: String, repositoryName: String, activityUserName: String,
tagName: String, commits: List[JGitUtil.CommitInfo])(implicit s: Session): Unit = tagName: String, commits: List[JGitUtil.CommitInfo]): Unit =
Activities insert Activity(userName, repositoryName, activityUserName, insertActivity(userName, repositoryName, activityUserName,
"create_tag", "create_tag",
s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]", s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]",
None, None)
currentDate)
def recordDeleteTagActivity(userName: String, repositoryName: String, activityUserName: String, def recordDeleteTagActivity(userName: String, repositoryName: String, activityUserName: String,
tagName: String, commits: List[JGitUtil.CommitInfo])(implicit s: Session): Unit = tagName: String, commits: List[JGitUtil.CommitInfo]): Unit =
Activities insert Activity(userName, repositoryName, activityUserName, insertActivity(userName, repositoryName, activityUserName,
"delete_tag", "delete_tag",
s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]", s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]",
None, None)
currentDate)
def recordCreateBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String) def recordCreateBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String): Unit =
(implicit s: Session): Unit = insertActivity(userName, repositoryName, activityUserName,
Activities insert Activity(userName, repositoryName, activityUserName,
"create_branch", "create_branch",
s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
None, None)
currentDate)
def recordDeleteBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String) def recordDeleteBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String): Unit =
(implicit s: Session): Unit = insertActivity(userName, repositoryName, activityUserName,
Activities insert Activity(userName, repositoryName, activityUserName,
"delete_branch", "delete_branch",
s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]", s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]",
None, None)
currentDate)
def recordForkActivity(userName: String, repositoryName: String, activityUserName: String, forkedUserName: String)(implicit s: Session): Unit = def recordForkActivity(userName: String, repositoryName: String, activityUserName: String, forkedUserName: String): Unit =
Activities insert Activity(userName, repositoryName, activityUserName, insertActivity(userName, repositoryName, activityUserName,
"fork", "fork",
s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${forkedUserName}/${repositoryName}]", s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${forkedUserName}/${repositoryName}]",
None, None)
currentDate)
def recordPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String) def recordPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit =
(implicit s: Session): Unit = insertActivity(userName, repositoryName, activityUserName,
Activities insert Activity(userName, repositoryName, activityUserName,
"open_pullreq", "open_pullreq",
s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(title), Some(title))
currentDate)
def recordMergeActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, message: String) def recordMergeActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, message: String): Unit =
(implicit s: Session): Unit = insertActivity(userName, repositoryName, activityUserName,
Activities insert Activity(userName, repositoryName, activityUserName,
"merge_pullreq", "merge_pullreq",
s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]", s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(message), Some(message))
currentDate)
private def insertActivity(userName: String, repositoryName: String, activityUserName: String, activityType: String,
message: String, additionalInfo: Option[String]): Unit = {
db.run(quote { query[Activity].insert })(Activity(
userName = userName,
repositoryName = repositoryName,
activityUserName = activityUserName,
activityType = activityType,
message = message,
additionalInfo = additionalInfo,
activityDate = currentDate
))
}
private def cut(value: String, length: Int): String = private def cut(value: String, length: Int): String =
if(value.length > length) value.substring(0, length) + "..." else value if(value.length > length) value.substring(0, length) + "..." else value

View File

@@ -1,35 +1,34 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.model.CommitComment import gitbucket.core.model.CommitComment
import gitbucket.core.util.{StringUtil, Implicits}
import scala.slick.jdbc.{StaticQuery => Q}
import Q.interpolation
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import profile.simple._ import profile.simple._
import Implicits._
import StringUtil._ import gitbucket.core.servlet.Database._
import io.getquill._
trait CommitsService { trait CommitsService {
def getCommitComments(owner: String, repository: String, commitId: String, includePullRequest: Boolean)(implicit s: Session) = def getCommitComments(owner: String, repository: String, commitId: String, includePullRequest: Boolean) =
CommitComments filter { db.run(quote { (owner: String, repository: String, commitId: String, includePullRequest: Boolean) =>
t => t.byCommit(owner, repository, commitId) && (t.issueId.isEmpty || includePullRequest) query[CommitComment].filter { t =>
} list t.userName == owner && t.repositoryName == repository && t.commitId == commitId && (t.issueId.isEmpty || includePullRequest)
}
})(owner, repository, commitId, includePullRequest)
def getCommitComment(owner: String, repository: String, commentId: String)(implicit s: Session) = def getCommitComment(owner: String, repository: String, commentId: String) =
if (commentId forall (_.isDigit)) if (commentId forall (_.isDigit))
CommitComments filter { t => db.run(quote { (owner: String, repository: String, commentId: Int) =>
t.byPrimaryKey(commentId.toInt) && t.byRepository(owner, repository) query[CommitComment].filter(t => t.userName == owner && t.repositoryName == repository && t.commentId == commentId)
} firstOption })(owner, repository, commentId.toInt).headOption
else else
None None
def createCommitComment(owner: String, repository: String, commitId: String, loginUser: String, def createCommitComment(owner: String, repository: String, commitId: String, loginUser: String,
content: String, fileName: Option[String], oldLine: Option[Int], newLine: Option[Int], content: String, fileName: Option[String], oldLine: Option[Int], newLine: Option[Int],
issueId: Option[Int])(implicit s: Session): Int = issueId: Option[Int])(implicit s: Session): Int =
CommitComments.autoInc insert CommitComment( CommitComments.autoInc insert CommitComment( // TODO Remain Slick code
userName = owner, userName = owner,
repositoryName = repository, repositoryName = repository,
commitId = commitId, commitId = commitId,
@@ -42,13 +41,12 @@ trait CommitsService {
updatedDate = currentDate, updatedDate = currentDate,
issueId = issueId) issueId = issueId)
def updateCommitComment(commentId: Int, content: String)(implicit s: Session) = def updateCommitComment(commentId: Int, content: String) =
CommitComments db.run(quote { (commentId: Int, content: String, updatedDate: java.util.Date) =>
.filter (_.byPrimaryKey(commentId)) query[CommitComment].filter(_.commentId == commentId).update(_.content -> content, _.updatedDate -> updatedDate)
.map { t => })(commentId, content, currentDate)
t.content -> t.updatedDate
}.update (content, currentDate) def deleteCommitComment(commentId: Int) =
db.run(quote { (commentId: Int) => query[CommitComment].filter(_.commentId == commentId).delete })(commentId)
def deleteCommitComment(commentId: Int)(implicit s: Session) =
CommitComments filter (_.byPrimaryKey(commentId)) delete
} }

View File

@@ -0,0 +1,91 @@
package gitbucket.core.service
import gitbucket.core.controller.Context
import gitbucket.core.model.Issue
import gitbucket.core.model.Profile._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.Notifier
import profile.simple._
trait HandleCommentService {
self: RepositoryService with IssuesService with ActivityService
with WebHookService with WebHookIssueCommentService with WebHookPullRequestService =>
/**
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
*/
def handleComment(issue: Issue, content: Option[String], repository: RepositoryService.RepositoryInfo, actionOpt: Option[String])
(implicit context: Context, s: Session) = {
defining(repository.owner, repository.name){ case (owner, name) =>
val userName = context.loginAccount.get.userName
val (action, recordActivity) = actionOpt
.collect {
case "close" if(!issue.closed) => true ->
(Some("close") -> Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
case "reopen" if(issue.closed) => false ->
(Some("reopen") -> Some(recordReopenIssueActivity _))
}
.map { case (closed, t) =>
updateClosed(owner, name, issue.issueId, closed)
t
}
.getOrElse(None -> None)
val commentId = (content, action) match {
case (None, None) => None
case (None, Some(action)) => Some(createComment(owner, name, userName, issue.issueId, action.capitalize, action))
case (Some(content), _) => Some(createComment(owner, name, userName, issue.issueId, content, action.map(_+ "_comment").getOrElse("comment")))
}
// record comment activity if comment is entered
content foreach {
(if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
(owner, name, userName, issue.issueId, _)
}
recordActivity foreach ( _ (owner, name, userName, issue.issueId, issue.title) )
// extract references and create refer comment
content.map { content =>
createReferComment(owner, name, issue, content, context.loginAccount.get)
}
// call web hooks
action match {
case None => commentId.map{ commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, context.loginAccount.get) }
case Some(act) => val webHookAction = act match {
case "open" => "opened"
case "reopen" => "reopened"
case "close" => "closed"
case _ => act
}
if(issue.isPullRequest){
callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, context.loginAccount.get)
} else {
callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, context.loginAccount.get)
}
}
// notifications
Notifier() match {
case f =>
content foreach {
f.toNotify(repository, issue, _){
Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
if(issue.isPullRequest) "pull" else "issues"}/${issue.issueId}#comment-${commentId.get}")
}
}
action foreach {
f.toNotify(repository, issue, _){
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issue.issueId}")
}
}
}
commentId.map( issue -> _ )
}
}
}

View File

@@ -1,6 +1,8 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.StringUtil
import profile.simple._ import profile.simple._
import gitbucket.core.util.StringUtil._ import gitbucket.core.util.StringUtil._
@@ -12,6 +14,7 @@ import Q.interpolation
trait IssuesService { trait IssuesService {
self: AccountService =>
import IssuesService._ import IssuesService._
def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) = def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) =
@@ -394,6 +397,29 @@ trait IssuesService {
} }
} }
} }
def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String, loginAccount: Account)(implicit s: Session) = {
StringUtil.extractIssueId(message).foreach { issueId =>
val content = fromIssue.issueId + ":" + fromIssue.title
if(getIssue(owner, repository, issueId).isDefined){
// Not add if refer comment already exist.
if(!getComments(owner, repository, issueId.toInt).exists { x => x.action == "refer" && x.content == content }) {
createComment(owner, repository, loginAccount.userName, issueId.toInt, content, "refer")
}
}
}
}
def createIssueComment(owner: String, repository: String, commit: CommitInfo)(implicit s: Session) = {
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")
}
}
}
}
} }
object IssuesService { object IssuesService {

View File

@@ -76,7 +76,7 @@ object ProtectedBranchService {
includeAdministrators: Boolean) extends AccountService with CommitStatusService { includeAdministrators: Boolean) extends AccountService with CommitStatusService {
def isAdministrator(pusher: String)(implicit session: Session): Boolean = def isAdministrator(pusher: String)(implicit session: Session): Boolean =
pusher == owner || getGroupMembers(owner).filter(gm => gm.userName == pusher && gm.isManager).nonEmpty pusher == owner || getGroupMembers(owner).filter(gm => gm.userName == pusher && gm.manager).nonEmpty
/** /**
* Can't be force pushed * Can't be force pushed

View File

@@ -0,0 +1,79 @@
package gitbucket.core.service
import gitbucket.core.model.Profile._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._
import gitbucket.core.util.JGitUtil
import gitbucket.core.model.Account
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.DirCache
import org.eclipse.jgit.lib.{FileMode, Constants}
import profile.simple._
trait RepositoryCreationService {
self: AccountService with RepositoryService with LabelsService with WikiService with ActivityService =>
def createRepository(loginAccount: Account, owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean)
(implicit s: Session) {
val ownerAccount = getAccountByUserName(owner).get
val loginUserName = loginAccount.userName
// Insert to the database at first
insertRepository(name, owner, description, isPrivate)
// Add collaborators for group repository
if(ownerAccount.groupAccount){
getGroupMembers(owner).foreach { member =>
addCollaborator(owner, name, member.userName)
}
}
// Insert default labels
insertDefaultLabels(owner, name)
// Create the actual repository
val gitdir = getRepositoryDir(owner, name)
JGitUtil.initRepository(gitdir)
if(createReadme){
using(Git.open(gitdir)){ git =>
val builder = DirCache.newInCore.builder()
val inserter = git.getRepository.newObjectInserter()
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
val content = if(description.nonEmpty){
name + "\n" +
"===============\n" +
"\n" +
description.get
} else {
name + "\n" +
"===============\n"
}
builder.add(JGitUtil.createDirCacheEntry("README.md", FileMode.REGULAR_FILE,
inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
builder.finish()
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit")
}
}
// Create Wiki repository
createWikiRepository(loginAccount, owner, name)
// Record activity
recordCreateRepositoryActivity(owner, name, loginUserName)
}
def insertDefaultLabels(userName: String, repositoryName: String)(implicit s: Session): Unit = {
createLabel(userName, repositoryName, "bug", "fc2929")
createLabel(userName, repositoryName, "duplicate", "cccccc")
createLabel(userName, repositoryName, "enhancement", "84b6eb")
createLabel(userName, repositoryName, "invalid", "e6e6e6")
createLabel(userName, repositoryName, "question", "cc317c")
createLabel(userName, repositoryName, "wontfix", "ffffff")
}
}

View File

@@ -19,7 +19,7 @@ trait RepositoryService { self: AccountService =>
* @param originRepositoryName specify for the forked repository. (default is None) * @param originRepositoryName specify for the forked repository. (default is None)
* @param originUserName 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, def insertRepository(repositoryName: String, userName: String, description: Option[String], isPrivate: Boolean,
originRepositoryName: Option[String] = None, originUserName: Option[String] = None, originRepositoryName: Option[String] = None, originUserName: Option[String] = None,
parentRepositoryName: Option[String] = None, parentUserName: Option[String] = None) parentRepositoryName: Option[String] = None, parentUserName: Option[String] = None)
(implicit s: Session): Unit = { (implicit s: Session): Unit = {
@@ -27,7 +27,7 @@ trait RepositoryService { self: AccountService =>
Repository( Repository(
userName = userName, userName = userName,
repositoryName = repositoryName, repositoryName = repositoryName,
isPrivate = isPrivate, `private` = isPrivate,
description = description, description = description,
defaultBranch = "master", defaultBranch = "master",
registeredDate = currentDate, registeredDate = currentDate,
@@ -115,7 +115,7 @@ trait RepositoryService { self: AccountService =>
repositoryName = newRepositoryName repositoryName = newRepositoryName
)) :_*) )) :_*)
if(account.isGroupAccount){ if(account.groupAccount){
Collaborators.insertAll(getGroupMembers(newUserName).map(m => Collaborator(newUserName, newRepositoryName, m.userName)) :_*) Collaborators.insertAll(getGroupMembers(newUserName).map(m => Collaborator(newUserName, newRepositoryName, m.userName)) :_*)
} else { } else {
Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
@@ -270,9 +270,9 @@ trait RepositoryService { self: AccountService =>
(implicit s: Session): List[RepositoryInfo] = { (implicit s: Session): List[RepositoryInfo] = {
(loginAccount match { (loginAccount match {
// for Administrators // for Administrators
case Some(x) if(x.isAdmin) => Repositories case Some(x) if(x.administrator) => Repositories
// for Normal Users // for Normal Users
case Some(x) if(!x.isAdmin) => case Some(x) if(!x.administrator) =>
Repositories filter { t => (t.isPrivate === false.bind) || (t.userName === x.userName) || 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) (Collaborators.filter { t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName === x.userName.bind)} exists)
} }
@@ -297,8 +297,8 @@ trait RepositoryService { self: AccountService =>
} }
private def getRepositoryManagers(userName: String)(implicit s: Session): Seq[String] = private def getRepositoryManagers(userName: String)(implicit s: Session): Seq[String] =
if(getAccountByUserName(userName).exists(_.isGroupAccount)){ if(getAccountByUserName(userName).exists(_.groupAccount)){
getGroupMembers(userName).collect { case x if(x.isManager) => x.userName } getGroupMembers(userName).collect { case x if(x.manager) => x.userName }
} else { } else {
Seq(userName) Seq(userName)
} }
@@ -365,7 +365,7 @@ trait RepositoryService { self: AccountService =>
def hasWritePermission(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = { def hasWritePermission(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
loginAccount match { loginAccount match {
case Some(a) if(a.isAdmin) => true case Some(a) if(a.administrator) => true
case Some(a) if(a.userName == owner) => true case Some(a) if(a.userName == owner) => true
case Some(a) if(getCollaborators(owner, repository).contains(a.userName)) => true case Some(a) if(getCollaborators(owner, repository).contains(a.userName)) => true
case _ => false case _ => false

View File

@@ -1,8 +1,13 @@
package gitbucket.core.service package gitbucket.core.service
import java.io.ByteArrayInputStream
import fr.brouillard.oss.security.xhub.XHub
import fr.brouillard.oss.security.xhub.XHub.{XHubDigest, XHubConverter}
import gitbucket.core.api._ import gitbucket.core.api._
import gitbucket.core.model.{WebHook, Account, Issue, PullRequest, IssueComment, WebHookEvent, CommitComment} import gitbucket.core.model.{WebHook, Account, Issue, PullRequest, IssueComment, WebHookEvent, CommitComment}
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import org.apache.http.client.utils.URLEncodedUtils
import profile.simple._ import profile.simple._
import gitbucket.core.util.JGitUtil.CommitInfo import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.RepositoryName import gitbucket.core.util.RepositoryName
@@ -33,8 +38,11 @@ trait WebHookService {
/** get All WebHook informations of repository event */ /** get All WebHook informations of repository event */
def getWebHooksByEvent(owner: String, repository: String, event: WebHook.Event)(implicit s: Session): List[WebHook] = def getWebHooksByEvent(owner: String, repository: String, event: WebHook.Event)(implicit s: Session): List[WebHook] =
WebHookEvents.filter(t => t.byRepository(owner, repository) && t.event === event.bind) WebHooks.filter(_.byRepository(owner, repository))
.list.map(t => WebHook(t.userName, t.repositoryName, t.url)) .innerJoin(WebHookEvents).on { (wh, whe) => whe.byWebHook(wh) }
.filter{ case (wh, whe) => whe.event === event.bind}
.map{ case (wh, whe) => wh }
.list.distinct
/** get All WebHook information from repository to url */ /** get All WebHook information from repository to url */
def getWebHook(owner: String, repository: String, url: String)(implicit s: Session): Option[(WebHook, Set[WebHook.Event])] = def getWebHook(owner: String, repository: String, url: String)(implicit s: Session): Option[(WebHook, Set[WebHook.Event])] =
@@ -44,14 +52,15 @@ trait WebHookService {
.map{ case (w,t) => w -> t.event } .map{ case (w,t) => w -> t.event }
.list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption .list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption
def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event])(implicit s: Session): Unit = { def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], token: Option[String])(implicit s: Session): Unit = {
WebHooks insert WebHook(owner, repository, url) WebHooks insert WebHook(owner, repository, url, token)
events.toSet.map{ event: WebHook.Event => events.toSet.map{ event: WebHook.Event =>
WebHookEvents insert WebHookEvent(owner, repository, url, event) WebHookEvents insert WebHookEvent(owner, repository, url, event)
} }
} }
def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event])(implicit s: Session): Unit = { def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], token: Option[String])(implicit s: Session): Unit = {
WebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => w.token).update(token)
WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete
events.toSet.map{ event: WebHook.Event => events.toSet.map{ event: WebHook.Event =>
WebHookEvents insert WebHookEvent(owner, repository, url, event) WebHookEvents insert WebHookEvent(owner, repository, url, event)
@@ -69,17 +78,17 @@ trait WebHookService {
} }
} }
def callWebHook(event: WebHook.Event, webHookURLs: List[WebHook], payload: WebHookPayload) def callWebHook(event: WebHook.Event, webHooks: List[WebHook], payload: WebHookPayload)
(implicit c: JsonFormat.Context): List[(WebHook, String, Future[HttpRequest], Future[HttpResponse])] = { (implicit c: JsonFormat.Context): List[(WebHook, String, Future[HttpRequest], Future[HttpResponse])] = {
import org.apache.http.impl.client.HttpClientBuilder import org.apache.http.impl.client.HttpClientBuilder
import ExecutionContext.Implicits.global import ExecutionContext.Implicits.global
import org.apache.http.protocol.HttpContext import org.apache.http.protocol.HttpContext
import org.apache.http.client.methods.HttpPost import org.apache.http.client.methods.HttpPost
if(webHookURLs.nonEmpty){ if(webHooks.nonEmpty){
val json = JsonFormat(payload) val json = JsonFormat(payload)
webHookURLs.map { webHookUrl => webHooks.map { webHook =>
val reqPromise = Promise[HttpRequest] val reqPromise = Promise[HttpRequest]
val f = Future { val f = Future {
val itcp = new org.apache.http.HttpRequestInterceptor{ val itcp = new org.apache.http.HttpRequestInterceptor{
@@ -89,19 +98,26 @@ trait WebHookService {
} }
try{ try{
val httpClient = HttpClientBuilder.create.addInterceptorLast(itcp).build val httpClient = HttpClientBuilder.create.addInterceptorLast(itcp).build
logger.debug(s"start web hook invocation for ${webHookUrl.url}") logger.debug(s"start web hook invocation for ${webHook.url}")
val httpPost = new HttpPost(webHookUrl.url) val httpPost = new HttpPost(webHook.url)
httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded") httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded")
httpPost.addHeader("X-Github-Event", event.name) httpPost.addHeader("X-Github-Event", event.name)
httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString) httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString)
val params: java.util.List[NameValuePair] = new java.util.ArrayList() val params: java.util.List[NameValuePair] = new java.util.ArrayList()
params.add(new BasicNameValuePair("payload", json)) params.add(new BasicNameValuePair("payload", json))
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")) def postContent = new UrlEncodedFormEntity(params, "UTF-8")
httpPost.setEntity(postContent)
if (!webHook.token.isEmpty) {
// TODO find a better way and see how to extract content from postContent
val contentAsBytes = URLEncodedUtils.format(params, "UTF-8").getBytes("UTF-8")
httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.orNull, contentAsBytes))
}
val res = httpClient.execute(httpPost) val res = httpClient.execute(httpPost)
httpPost.releaseConnection() httpPost.releaseConnection()
logger.debug(s"end web hook invocation for ${webHookUrl}") logger.debug(s"end web hook invocation for ${webHook}")
res res
}catch{ }catch{
case e:Throwable => { case e:Throwable => {
@@ -113,12 +129,12 @@ trait WebHookService {
} }
} }
f.onSuccess { f.onSuccess {
case s => logger.debug(s"Success: web hook request to ${webHookUrl.url}") case s => logger.debug(s"Success: web hook request to ${webHook.url}")
} }
f.onFailure { f.onFailure {
case t => logger.error(s"Failed: web hook request to ${webHookUrl.url}", t) case t => logger.error(s"Failed: web hook request to ${webHook.url}", t)
} }
(webHookUrl, json, reqPromise.future, f) (webHook, json, reqPromise.future, f)
} }
} else { } else {
Nil Nil

View File

@@ -21,6 +21,7 @@ object AutoUpdate {
* The history of versions. A head of this sequence is the current GitBucket version. * The history of versions. A head of this sequence is the current GitBucket version.
*/ */
val versions = Seq( val versions = Seq(
new Version(3, 13),
new Version(3, 12), new Version(3, 12),
new Version(3, 11), new Version(3, 11),
new Version(3, 10), new Version(3, 10),

View File

@@ -76,14 +76,14 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
case Array(_, repositoryOwner, repositoryName, _*) => case Array(_, repositoryOwner, repositoryName, _*) =>
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", "")) match { getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", "")) match {
case Some(repository) => { case Some(repository) => {
if(!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess){ if(!isUpdating && !repository.repository.`private` && settings.allowAnonymousAccess){
chain.doFilter(request, response) chain.doFilter(request, response)
} else { } else {
val passed = for { val passed = for {
auth <- Option(request.getHeader("Authorization")) auth <- Option(request.getHeader("Authorization"))
Array(username, password) = decodeAuthHeader(auth).split(":", 2) Array(username, password) = decodeAuthHeader(auth).split(":", 2)
account <- authenticate(settings, username, password) account <- authenticate(settings, username, password)
} yield if(isUpdating || repository.repository.isPrivate){ } yield if(isUpdating || repository.repository.`private`){
if(hasWritePermission(repository.owner, repository.name, Some(account))){ if(hasWritePermission(repository.owner, repository.name, Some(account))){
request.setAttribute(Keys.Request.UserName, account.userName) request.setAttribute(Keys.Request.UserName, account.userName)
true true

View File

@@ -10,7 +10,6 @@ import gitbucket.core.service.WebHookService._
import gitbucket.core.service._ import gitbucket.core.service._
import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util._ import gitbucket.core.util._
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
@@ -168,7 +167,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
if (!existIds.contains(commit.id) && !pushedIds.contains(commit.id)) { if (!existIds.contains(commit.id) && !pushedIds.contains(commit.id)) {
if (issueCount > 0) { if (issueCount > 0) {
pushedIds.add(commit.id) pushedIds.add(commit.id)
createIssueComment(commit) createIssueComment(owner, repository, commit)
// close issues // close issues
if(refName(1) == "heads" && branchName == defaultBranch && command.getType == ReceiveCommand.Type.UPDATE){ if(refName(1) == "heads" && branchName == defaultBranch && command.getType == ReceiveCommand.Type.UPDATE){
closeIssuesFromMessage(commit.fullMessage, pusher, owner, repository) closeIssuesFromMessage(commit.fullMessage, pusher, owner, repository)
@@ -230,13 +229,4 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
} }
} }
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")
}
}
}
}
} }

View File

@@ -4,10 +4,14 @@ import javax.servlet._
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
import com.mchange.v2.c3p0.ComboPooledDataSource import com.mchange.v2.c3p0.ComboPooledDataSource
import gitbucket.core.util.DatabaseConfig import gitbucket.core.util.DatabaseConfig
import io.getquill._
import io.getquill.naming.SnakeCase
import io.getquill.sources.sql.idiom.H2Dialect
import org.scalatra.ScalatraBase import org.scalatra.ScalatraBase
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import slick.jdbc.JdbcBackend.{Database => SlickDatabase, Session} import slick.jdbc.JdbcBackend.{Database => SlickDatabase, Session}
import gitbucket.core.util.Keys import gitbucket.core.util.Keys
import Database._
/** /**
* Controls the transaction with the open session in view pattern. * Controls the transaction with the open session in view pattern.
@@ -25,17 +29,20 @@ class TransactionFilter extends Filter {
// assets don't need transaction // assets don't need transaction
chain.doFilter(req, res) chain.doFilter(req, res)
} else { } else {
Database() withTransaction { session => db.transaction {
// Register Scalatra error callback to rollback transaction // TODO Delete after moving to quill
ScalatraBase.onFailure { _ => Database() withTransaction { session =>
logger.debug("Rolled back transaction") // Register Scalatra error callback to rollback transaction
session.rollback() ScalatraBase.onFailure { _ =>
}(req.asInstanceOf[HttpServletRequest]) logger.debug("Rolled back transaction")
session.rollback()
}(req.asInstanceOf[HttpServletRequest])
logger.debug("begin transaction") logger.debug("begin transaction")
req.setAttribute(Keys.Request.DBSession, session) req.setAttribute(Keys.Request.DBSession, session)
chain.doFilter(req, res) chain.doFilter(req, res)
logger.debug("end transaction") logger.debug("end transaction")
}
} }
} }
} }
@@ -46,6 +53,9 @@ object Database {
private val logger = LoggerFactory.getLogger(Database.getClass) private val logger = LoggerFactory.getLogger(Database.getClass)
lazy val db = source(new JdbcSourceConfig[H2Dialect, SnakeCase]("db"))
// TODO Delete after moving to quill
private val dataSource: ComboPooledDataSource = { private val dataSource: ComboPooledDataSource = {
val ds = new ComboPooledDataSource val ds = new ComboPooledDataSource
ds.setDriverClass(DatabaseConfig.driver) ds.setDriverClass(DatabaseConfig.driver)
@@ -56,15 +66,19 @@ object Database {
ds ds
} }
private val db: SlickDatabase = { // TODO Delete after moving to quill
private val slickDatabase: SlickDatabase = {
SlickDatabase.forDataSource(dataSource) SlickDatabase.forDataSource(dataSource)
} }
def apply(): SlickDatabase = db // TODO Delete after moving to quill
def apply(): SlickDatabase = slickDatabase
// TODO Delete after moving to quill
def getSession(req: ServletRequest): Session = def getSession(req: ServletRequest): Session =
req.getAttribute(Keys.Request.DBSession).asInstanceOf[Session] req.getAttribute(Keys.Request.DBSession).asInstanceOf[Session]
// TODO Delete after moving to quill
def closeDataSource(): Unit = dataSource.close def closeDataSource(): Unit = dataSource.close
} }

View File

@@ -92,7 +92,7 @@ class DefaultGitUploadPack(owner: String, repoName: String) extends DefaultGitCo
override protected def runTask(user: String)(implicit session: Session): Unit = { override protected def runTask(user: String)(implicit session: Session): Unit = {
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).foreach { repositoryInfo => getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).foreach { repositoryInfo =>
if(!repositoryInfo.repository.isPrivate || isWritableUser(user, repositoryInfo)){ if(!repositoryInfo.repository.`private` || isWritableUser(user, repositoryInfo)){
using(Git.open(getRepositoryDir(owner, repoName))) { git => using(Git.open(getRepositoryDir(owner, repoName))) { git =>
val repository = git.getRepository val repository = git.getRepository
val upload = new UploadPack(repository) val upload = new UploadPack(repository)

View File

@@ -17,7 +17,7 @@ trait OneselfAuthenticator { self: ControllerBase =>
{ {
defining(request.paths){ paths => defining(request.paths){ paths =>
context.loginAccount match { context.loginAccount match {
case Some(x) if(x.isAdmin) => action case Some(x) if(x.administrator) => action
case Some(x) if(paths(0) == x.userName) => action case Some(x) if(paths(0) == x.userName) => action
case _ => Unauthorized() case _ => Unauthorized()
} }
@@ -38,10 +38,10 @@ trait OwnerAuthenticator { self: ControllerBase with RepositoryService with Acco
defining(request.paths){ paths => defining(request.paths){ paths =>
getRepository(paths(0), paths(1)).map { repository => getRepository(paths(0), paths(1)).map { repository =>
context.loginAccount match { context.loginAccount match {
case Some(x) if(x.isAdmin) => action(repository) case Some(x) if(x.administrator) => action(repository)
case Some(x) if(repository.owner == x.userName) => action(repository) case Some(x) if(repository.owner == x.userName) => action(repository)
case Some(x) if(getGroupMembers(repository.owner).exists { member => case Some(x) if(getGroupMembers(repository.owner).exists { member =>
member.userName == x.userName && member.isManager == true member.userName == x.userName && member.manager == true
}) => action(repository) }) => action(repository)
case _ => Unauthorized() case _ => Unauthorized()
} }
@@ -78,7 +78,7 @@ trait AdminAuthenticator { self: ControllerBase =>
private def authenticate(action: => Any) = { private def authenticate(action: => Any) = {
{ {
context.loginAccount match { context.loginAccount match {
case Some(x) if(x.isAdmin) => action case Some(x) if(x.administrator) => action
case _ => Unauthorized() case _ => Unauthorized()
} }
} }
@@ -97,7 +97,7 @@ trait CollaboratorsAuthenticator { self: ControllerBase with RepositoryService =
defining(request.paths){ paths => defining(request.paths){ paths =>
getRepository(paths(0), paths(1)).map { repository => getRepository(paths(0), paths(1)).map { repository =>
context.loginAccount match { context.loginAccount match {
case Some(x) if(x.isAdmin) => action(repository) case Some(x) if(x.administrator) => action(repository)
case Some(x) if(paths(0) == x.userName) => 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 Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
case _ => Unauthorized() case _ => Unauthorized()
@@ -119,11 +119,11 @@ trait ReferrerAuthenticator { self: ControllerBase with RepositoryService =>
{ {
defining(request.paths){ paths => defining(request.paths){ paths =>
getRepository(paths(0), paths(1)).map { repository => getRepository(paths(0), paths(1)).map { repository =>
if(!repository.repository.isPrivate){ if(!repository.repository.`private`){
action(repository) action(repository)
} else { } else {
context.loginAccount match { context.loginAccount match {
case Some(x) if(x.isAdmin) => action(repository) case Some(x) if(x.administrator) => action(repository)
case Some(x) if(paths(0) == x.userName) => 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 Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
case _ => Unauthorized() case _ => Unauthorized()
@@ -147,8 +147,8 @@ trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService =
defining(request.paths){ paths => defining(request.paths){ paths =>
getRepository(paths(0), paths(1)).map { repository => getRepository(paths(0), paths(1)).map { repository =>
context.loginAccount match { context.loginAccount match {
case Some(x) if(x.isAdmin) => action(repository) case Some(x) if(x.administrator) => action(repository)
case Some(x) if(!repository.repository.isPrivate) => action(repository) case Some(x) if(!repository.repository.`private`) => action(repository)
case Some(x) if(paths(0) == x.userName) => 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 Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
case _ => Unauthorized() case _ => Unauthorized()
@@ -171,7 +171,7 @@ trait GroupManagerAuthenticator { self: ControllerBase with AccountService =>
defining(request.paths){ paths => defining(request.paths){ paths =>
context.loginAccount match { context.loginAccount match {
case Some(x) if(getGroupMembers(paths(0)).exists { member => case Some(x) if(getGroupMembers(paths(0)).exists { member =>
member.userName == x.userName && member.isManager member.userName == x.userName && member.manager
}) => action }) => action
case _ => Unauthorized() case _ => Unauthorized()
} }

View File

@@ -30,7 +30,7 @@ trait Notifier extends RepositoryService with AccountService with IssuesService
) )
.distinct .distinct
.withFilter ( _ != context.loginAccount.get.userName ) // the operation in person is excluded .withFilter ( _ != context.loginAccount.get.userName ) // the operation in person is excluded
.foreach ( getAccountByUserName(_) filterNot (_.isGroupAccount) filterNot (LDAPUtil.isDummyMailAddress(_)) foreach (x => notify(x.mailAddress)) ) .foreach ( getAccountByUserName(_) filterNot (_.groupAccount) filterNot (LDAPUtil.isDummyMailAddress(_)) foreach (x => notify(x.mailAddress)) )
} }

View File

@@ -32,7 +32,7 @@
</ul> </ul>
@helper.html.account("memberName", 200) @helper.html.account("memberName", 200)
<input type="button" class="btn btn-default" value="Add" id="addMember"/> <input type="button" class="btn btn-default" value="Add" id="addMember"/>
<input type="hidden" id="members" name="members" value="@members.map(member => member.userName + ":" + member.isManager).mkString(",")"/> <input type="hidden" id="members" name="members" value="@members.map(member => member.userName + ":" + member.manager).mkString(",")"/>
<div> <div>
<span class="error" id="error-members"></span> <span class="error" id="error-members"></span>
</div> </div>
@@ -103,7 +103,7 @@ $(function(){
}); });
@members.map { member => @members.map { member =>
addMemberHTML('@member.userName', @member.isManager); addMemberHTML('@member.userName', @member.manager);
} }
function addMemberHTML(userName, isManager){ function addMemberHTML(userName, isManager){

View File

@@ -31,7 +31,7 @@
<div class="col-md-8"> <div class="col-md-8">
<ul class="nav nav-tabs" style="margin-bottom: 5px;"> <ul class="nav nav-tabs" style="margin-bottom: 5px;">
<li@if(active == "repositories"){ class="active"}><a href="@url(account.userName)?tab=repositories">Repositories</a></li> <li@if(active == "repositories"){ class="active"}><a href="@url(account.userName)?tab=repositories">Repositories</a></li>
@if(account.isGroupAccount){ @if(account.groupAccount){
<li@if(active == "members"){ class="active"}><a href="@url(account.userName)?tab=members">Members</a></li> <li@if(active == "members"){ class="active"}><a href="@url(account.userName)?tab=members">Members</a></li>
} else { } else {
<li@if(active == "activity"){ class="active"}><a href="@url(account.userName)?tab=activity">Public Activity</a></li> <li@if(active == "activity"){ class="active"}><a href="@url(account.userName)?tab=activity">Public Activity</a></li>
@@ -43,9 +43,9 @@
</div> </div>
</li> </li>
} }
@if(loginAccount.isDefined && account.isGroupAccount && isGroupManager){ @if(loginAccount.isDefined && account.groupAccount && isGroupManager){
<li class="pull-right"> <li class="pull-right">
<div class="button-group"> <div class="button-groRepiosup">
<a href="@url(account.userName)/_editgroup" class="btn btn-default">Edit Group</a> <a href="@url(account.userName)/_editgroup" class="btn btn-default">Edit Group</a>
</div> </div>
</li> </li>

View File

@@ -15,7 +15,7 @@
<div class="repository-content"> <div class="repository-content">
<div class="block-header"> <div class="block-header">
<a href="@url(repository)">@repository.name</a> <a href="@url(repository)">@repository.name</a>
@if(repository.repository.isPrivate){ @if(repository.repository.`private`){
<i class="octicon octicon-lock"></i> <i class="octicon octicon-lock"></i>
} }
</div> </div>

View File

@@ -13,7 +13,7 @@
<input type="text" name="userName" id="userName" class="form-control" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/> <input type="text" name="userName" id="userName" class="form-control" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
@if(account.isDefined){ @if(account.isDefined){
<label for="removed"> <label for="removed">
<input type="checkbox" name="removed" id="removed" value="true" @if(account.get.isRemoved){checked}/> <input type="checkbox" name="removed" id="removed" value="true" @if(account.get.removed){checked}/>
Disable Disable
</label> </label>
<div> <div>
@@ -53,10 +53,10 @@
<fieldset class="form-group"> <fieldset class="form-group">
<label class="strong">User Type:</label> <label class="strong">User Type:</label>
<label class="radio" for="userType_Normal"> <label class="radio" for="userType_Normal">
<input type="radio" name="isAdmin" id="userType_Normal" value="false"@if(account.isEmpty || !account.get.isAdmin){ checked}/> Normal <input type="radio" name="isAdmin" id="userType_Normal" value="false"@if(account.isEmpty || !account.get.administrator){ checked}/> Normal
</label> </label>
<label class="radio" for="userType_Admin"> <label class="radio" for="userType_Admin">
<input type="radio" name="isAdmin" id="userType_Admin" value="true"@if(account.isDefined && account.get.isAdmin){ checked}/> Administrator <input type="radio" name="isAdmin" id="userType_Admin" value="true"@if(account.isDefined && account.get.administrator){ checked}/> Administrator
</label> </label>
</fieldset> </fieldset>
<fieldset class="form-group"> <fieldset class="form-group">

View File

@@ -14,7 +14,7 @@
<input type="text" name="groupName" id="groupName" class="form-control" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/> <input type="text" name="groupName" id="groupName" class="form-control" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
@if(account.isDefined){ @if(account.isDefined){
<label for="removed"> <label for="removed">
<input type="checkbox" name="removed" id="removed" value="true" @if(account.get.isRemoved){checked}/> <input type="checkbox" name="removed" id="removed" value="true" @if(account.get.removed){checked}/>
Disable Disable
</label> </label>
} }
@@ -38,7 +38,7 @@
</ul> </ul>
@helper.html.account("memberName", 200) @helper.html.account("memberName", 200)
<input type="button" class="btn btn-default" value="Add" id="addMember"/> <input type="button" class="btn btn-default" value="Add" id="addMember"/>
<input type="hidden" id="members" name="members" value="@members.map(member => member.userName + ":" + member.isManager).mkString(",")"/> <input type="hidden" id="members" name="members" value="@members.map(member => member.userName + ":" + member.manager).mkString(",")"/>
<div> <div>
<span class="error" id="error-members"></span> <span class="error" id="error-members"></span>
</div> </div>
@@ -98,7 +98,7 @@ $(function(){
}); });
@members.map { member => @members.map { member =>
addMemberHTML('@member.userName', @member.isManager); addMemberHTML('@member.userName', @member.manager);
} }
function addMemberHTML(userName, isManager){ function addMemberHTML(userName, isManager){

View File

@@ -14,9 +14,9 @@
<table class="table table-bordered table-hover"> <table class="table table-bordered table-hover">
@users.map { account => @users.map { account =>
<tr> <tr>
<td @if(account.isRemoved){style="background-color: #dddddd;"}> <td @if(account.removed){style="background-color: #dddddd;"}>
<div class="pull-right"> <div class="pull-right">
@if(account.isGroupAccount){ @if(account.groupAccount){
<a href="@path/admin/users/@account.userName/_editgroup">Edit</a> <a href="@path/admin/users/@account.userName/_editgroup">Edit</a>
} else { } else {
<a href="@path/admin/users/@account.userName/_edituser">Edit</a> <a href="@path/admin/users/@account.userName/_edituser">Edit</a>
@@ -25,16 +25,16 @@
<div class="strong"> <div class="strong">
@avatar(account.userName, 20) @avatar(account.userName, 20)
<a href="@url(account.userName)">@account.userName</a> <a href="@url(account.userName)">@account.userName</a>
@if(account.isGroupAccount){ @if(account.groupAccount){
(Group) (Group)
} else { } else {
@if(account.isAdmin){ @if(account.administrator){
(Administrator) (Administrator)
} else { } else {
(Normal) (Normal)
} }
} }
@if(account.isGroupAccount){ @if(account.groupAccount){
@members(account.userName).map { userName => @members(account.userName).map { userName =>
@avatar(userName, 20, tooltip = true) @avatar(userName, 20, tooltip = true)
} }
@@ -42,7 +42,7 @@
</div> </div>
<div> <div>
<hr> <hr>
@if(!account.isGroupAccount){ @if(!account.groupAccount){
<i class="octicon octicon-mail"></i> @account.mailAddress <i class="octicon octicon-mail"></i> @account.mailAddress
} }
@account.url.map { url => @account.url.map { url =>
@@ -52,7 +52,7 @@
<div> <div>
<span class="muted">Registered:</span> @datetime(account.registeredDate) <span class="muted">Registered:</span> @datetime(account.registeredDate)
<span class="muted">Updated:</span> @datetime(account.updatedDate) <span class="muted">Updated:</span> @datetime(account.updatedDate)
@if(!account.isGroupAccount){ @if(!account.groupAccount){
<span class="muted">Last Login:</span> @account.lastLoginDate.map(datetime) <span class="muted">Last Login:</span> @account.lastLoginDate.map(datetime)
} }
</div> </div>

View File

@@ -2,7 +2,7 @@
@import gitbucket.core.service.RepositoryService @import gitbucket.core.service.RepositoryService
@import context._ @import context._
@import gitbucket.core.view.helpers._ @import gitbucket.core.view.helpers._
@if(repository.repository.isPrivate){ @if(repository.repository.`private`){
<i class="@{if(large){"mega-"}}octicon octicon-lock"></i> <i class="@{if(large){"mega-"}}octicon octicon-lock"></i>
} else { } else {
@if(repository.repository.originUserName.isDefined){ @if(repository.repository.originUserName.isDefined){

View File

@@ -138,7 +138,7 @@
<span class="discussion-item-icon"><i class="octicon octicon-circle-slash"></i></span> <span class="discussion-item-icon"><i class="octicon octicon-circle-slash"></i></span>
@avatar(comment.commentedUserName, 16) @avatar(comment.commentedUserName, 16)
@user(comment.commentedUserName, styleClass="username strong") @user(comment.commentedUserName, styleClass="username strong")
close @issueOrPullRequest() closed this @issueOrPullRequest()
@helper.html.datetimeago(comment.registeredDate) @helper.html.datetimeago(comment.registeredDate)
</div> </div>
</div> </div>

View File

@@ -36,7 +36,7 @@
<script src="@assets/vendors/facebox/facebox.js"></script> <script src="@assets/vendors/facebox/facebox.js"></script>
<script src="@assets/vendors/jquery-hotkeys/jquery.hotkeys.js"></script> <script src="@assets/vendors/jquery-hotkeys/jquery.hotkeys.js"></script>
@repository.map { repository => @repository.map { repository =>
@if(!repository.repository.isPrivate){ @if(!repository.repository.`private`){
<meta name="go-import" content="@context.baseUrl.replaceFirst("^https?://", "")/@repository.owner/@repository.name git @repository.httpUrl" /> <meta name="go-import" content="@context.baseUrl.replaceFirst("^https?://", "")/@repository.owner/@repository.name git @repository.httpUrl" />
} }
} }
@@ -92,7 +92,7 @@
<ul class="dropdown-menu pull-right"> <ul class="dropdown-menu pull-right">
<li><a href="@url(loginAccount.get.userName)">Your profile</a></li> <li><a href="@url(loginAccount.get.userName)">Your profile</a></li>
<li><a href="@url(loginAccount.get.userName)/_edit">Account settings</a></li> <li><a href="@url(loginAccount.get.userName)/_edit">Account settings</a></li>
@if(loginAccount.get.isAdmin){ @if(loginAccount.get.administrator){
<li><a href="@path/admin/users">System administration</a></li> <li><a href="@path/admin/users">System administration</a></li>
} }
<li><a href="@path/signout">Sign out</a></li> <li><a href="@path/signout">Sign out</a></li>

View File

@@ -63,7 +63,7 @@
@menuitem("/issues", "issues" , "issue-opened" , "Issues", repository.issueCount) @menuitem("/issues", "issues" , "issue-opened" , "Issues", repository.issueCount)
@menuitem("/pulls" , "pulls" , "git-pull-request" , "Pull Requests", repository.pullCount) @menuitem("/pulls" , "pulls" , "git-pull-request" , "Pull Requests", repository.pullCount)
@menuitem("/wiki" , "wiki" , "book" , "Wiki") @menuitem("/wiki" , "wiki" , "book" , "Wiki")
@if(loginAccount.isDefined && (loginAccount.get.isAdmin || repository.managers.contains(loginAccount.get.userName))){ @if(loginAccount.isDefined && (loginAccount.get.administrator || repository.managers.contains(loginAccount.get.userName))){
@menuitem("/settings" , "settings" , "tools", "Settings") @menuitem("/settings" , "settings" , "tools", "Settings")
} }
</ul> </ul>

View File

@@ -160,7 +160,7 @@
</a> </a>
} }
} else { } else {
<a href="@url(repository)/blob@{(encodeRefName(branch) :: pathList).mkString("/", "/", "/")}@file.name">@file.name</a> <a href="@url(repository)/blob@{(branch :: pathList).map(encodeRefName).mkString("/", "/", "/")}@{encodeRefName(file.name)}">@file.name</a>
} }
</td> </td>
<td class="mute"> <td class="mute">

View File

@@ -30,6 +30,11 @@
} }
<button class="btn btn-default" id="test">Test Hook</button> <button class="btn btn-default" id="test">Test Hook</button>
</fieldset> </fieldset>
<fieldset class="form-group">
<label class="strong">Security Token</label>
<div></div>
<input type="text" name="token" id="token" placeholder="leave blank for no X-Hub-Signature usage" value="@webHook.token" class="form-control" style="display: inline; width: 500px; vertical-align: middle;" />
</fieldset>
<hr /> <hr />
<label class="strong">Which events would you like to trigger this webhook?</label> <label class="strong">Which events would you like to trigger this webhook?</label>
<div> <div>
@@ -123,6 +128,7 @@ $(function(){
e.stopImmediatePropagation(); e.stopImmediatePropagation();
e.preventDefault(); e.preventDefault();
var url = this.form.url.value; var url = this.form.url.value;
var token = this.form.token.value;
if(!/^https?:\/\/.+/.test(url)){ if(!/^https?:\/\/.+/.test(url)){
alert("invalid url"); alert("invalid url");
return; return;
@@ -132,7 +138,7 @@ $(function(){
$("#test-report").hide(); $("#test-report").hide();
$.ajax({ $.ajax({
method:'POST', method:'POST',
url:'@url(repository)/settings/hooks/test?url=' + encodeURIComponent(url), url:'@url(repository)/settings/hooks/test?url=' + encodeURIComponent(url) + '&token=' + encodeURIComponent(token),
success: function(e){ success: function(e){
//console.log(e); //console.log(e);
$('#test-report-tab a:first').tab('show'); $('#test-report-tab a:first').tab('show');

View File

@@ -21,7 +21,7 @@
<fieldset class="margin"> <fieldset class="margin">
<label class="radio"> <label class="radio">
<input type="radio" name="isPrivate" value="false" <input type="radio" name="isPrivate" value="false"
@if(!repository.repository.isPrivate ){ checked } @if(!repository.repository.`private` ){ checked }
@if(repository.repository.parentUserName.isDefined){ disabled } @if(repository.repository.parentUserName.isDefined){ disabled }
> >
<span class="strong"><i class="octicon octicon-repo"></i> Public</span><br> <span class="strong"><i class="octicon octicon-repo"></i> Public</span><br>
@@ -31,7 +31,7 @@
</label> </label>
<label class="radio"> <label class="radio">
<input type="radio" name="isPrivate" value="true" <input type="radio" name="isPrivate" value="true"
@if(repository.repository.isPrivate ){ checked } @if(repository.repository.`private` ){ checked }
@if(repository.repository.parentUserName.isDefined){ disabled } @if(repository.repository.parentUserName.isDefined){ disabled }
> >
<span class="strong"><i class="octicon octicon-lock"></i> Private</span><br> <span class="strong"><i class="octicon octicon-lock"></i> Private</span><br>

View File

@@ -33,7 +33,7 @@ class CommitStatusServiceSpec extends FunSuite with ServiceSpecBase with CommitS
now = fixture1.registeredDate) now = fixture1.registeredDate)
test("createCommitState can insert and update") { withTestDB { implicit session => test("createCommitState can insert and update") { withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator) val tester = generateNewAccount(fixture1.creator)
createRepository(fixture1.repositoryName,fixture1.userName,None,false) insertRepository(fixture1.repositoryName,fixture1.userName,None,false)
val id = generateFixture1(tester:Account) val id = generateFixture1(tester:Account)
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id))) assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id)))
// other one can update // other one can update
@@ -60,14 +60,14 @@ class CommitStatusServiceSpec extends FunSuite with ServiceSpecBase with CommitS
test("getCommitStatus can find by commitId and context") { withTestDB { implicit session => test("getCommitStatus can find by commitId and context") { withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator) val tester = generateNewAccount(fixture1.creator)
createRepository(fixture1.repositoryName,fixture1.userName,None,false) insertRepository(fixture1.repositoryName,fixture1.userName,None,false)
val id = generateFixture1(tester:Account) val id = generateFixture1(tester:Account)
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, fixture1.commitId, fixture1.context) == Some(fixture1.copy(commitStatusId=id))) assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, fixture1.commitId, fixture1.context) == Some(fixture1.copy(commitStatusId=id)))
}} }}
test("getCommitStatus can find by commitStatusId") { withTestDB { implicit session => test("getCommitStatus can find by commitStatusId") { withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator) val tester = generateNewAccount(fixture1.creator)
createRepository(fixture1.repositoryName,fixture1.userName,None,false) insertRepository(fixture1.repositoryName,fixture1.userName,None,false)
val id = generateFixture1(tester:Account) val id = generateFixture1(tester:Account)
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id))) assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id)))
}} }}

View File

@@ -3,7 +3,7 @@ package gitbucket.core.service
import gitbucket.core.model._ import gitbucket.core.model._
import org.scalatest.FunSpec import org.scalatest.FunSpec
class PullRequestServiceSpec extends FunSpec with ServiceSpecBase with PullRequestService with IssuesService { class PullRequestServiceSpec extends FunSpec with ServiceSpecBase with PullRequestService with IssuesService with AccountService {
def swap(r: (Issue, PullRequest)) = (r._2 -> r._1) def swap(r: (Issue, PullRequest)) = (r._2 -> r._1)

View File

@@ -6,7 +6,7 @@ import org.scalatest.FunSuite
class RepositoryServiceSpec extends FunSuite with ServiceSpecBase with RepositoryService with AccountService{ class RepositoryServiceSpec extends FunSuite with ServiceSpecBase with RepositoryService with AccountService{
test("renameRepository can rename CommitState, ProtectedBranches") { withTestDB { implicit session => test("renameRepository can rename CommitState, ProtectedBranches") { withTestDB { implicit session =>
val tester = generateNewAccount("tester") val tester = generateNewAccount("tester")
createRepository("repo", "root", None, false) insertRepository("repo", "root", None, false)
val service = new CommitStatusService with ProtectedBranchService {} val service = new CommitStatusService with ProtectedBranchService {}
val id = service.createCommitStatus( val id = service.createCommitStatus(
userName = "root", userName = "root",

View File

@@ -42,7 +42,7 @@ trait ServiceSpecBase {
def generateNewUserWithDBRepository(userName:String, repositoryName:String)(implicit s:Session):Account = { def generateNewUserWithDBRepository(userName:String, repositoryName:String)(implicit s:Session):Account = {
val ac = AccountService.getAccountByUserName(userName).getOrElse(generateNewAccount(userName)) val ac = AccountService.getAccountByUserName(userName).getOrElse(generateNewAccount(userName))
dummyService.createRepository(repositoryName, userName, None, false) dummyService.insertRepository(repositoryName, userName, None, false)
ac ac
} }

View File

@@ -16,12 +16,12 @@ class WebHookServiceSpec extends FunSuite with ServiceSpecBase {
val (issue3, pullreq3) = generateNewPullRequest("user3/repo3/master3", "user2/repo2/master2", loginUser="root") val (issue3, pullreq3) = generateNewPullRequest("user3/repo3/master3", "user2/repo2/master2", loginUser="root")
val (issue32, pullreq32) = generateNewPullRequest("user3/repo3/master32", "user2/repo2/master2", loginUser="root") val (issue32, pullreq32) = generateNewPullRequest("user3/repo3/master32", "user2/repo2/master2", loginUser="root")
generateNewPullRequest("user2/repo2/master2", "user1/repo1/master2") generateNewPullRequest("user2/repo2/master2", "user1/repo1/master2")
service.addWebHook("user1", "repo1", "webhook1-1", Set(WebHook.PullRequest)) service.addWebHook("user1", "repo1", "webhook1-1", Set(WebHook.PullRequest), Some("key"))
service.addWebHook("user1", "repo1", "webhook1-2", Set(WebHook.PullRequest)) service.addWebHook("user1", "repo1", "webhook1-2", Set(WebHook.PullRequest), Some("key"))
service.addWebHook("user2", "repo2", "webhook2-1", Set(WebHook.PullRequest)) service.addWebHook("user2", "repo2", "webhook2-1", Set(WebHook.PullRequest), Some("key"))
service.addWebHook("user2", "repo2", "webhook2-2", Set(WebHook.PullRequest)) service.addWebHook("user2", "repo2", "webhook2-2", Set(WebHook.PullRequest), Some("key"))
service.addWebHook("user3", "repo3", "webhook3-1", Set(WebHook.PullRequest)) service.addWebHook("user3", "repo3", "webhook3-1", Set(WebHook.PullRequest), Some("key"))
service.addWebHook("user3", "repo3", "webhook3-2", Set(WebHook.PullRequest)) service.addWebHook("user3", "repo3", "webhook3-2", Set(WebHook.PullRequest), Some("key"))
assert(service.getPullRequestsByRequestForWebhook("user1","repo1","master1") == Map.empty) assert(service.getPullRequestsByRequestForWebhook("user1","repo1","master1") == Map.empty)
@@ -43,33 +43,33 @@ class WebHookServiceSpec extends FunSuite with ServiceSpecBase {
test("add and get and update and delete") { withTestDB { implicit session => test("add and get and update and delete") { withTestDB { implicit session =>
val user1 = generateNewUserWithDBRepository("user1","repo1") val user1 = generateNewUserWithDBRepository("user1","repo1")
service.addWebHook("user1", "repo1", "http://example.com", Set(WebHook.PullRequest)) service.addWebHook("user1", "repo1", "http://example.com", Set(WebHook.PullRequest), Some("key"))
assert(service.getWebHooks("user1", "repo1") == List((WebHook("user1","repo1","http://example.com"),Set(WebHook.PullRequest)))) assert(service.getWebHooks("user1", "repo1") == List((WebHook("user1","repo1","http://example.com", Some("key")),Set(WebHook.PullRequest))))
assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((WebHook("user1","repo1","http://example.com"),Set(WebHook.PullRequest)))) assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((WebHook("user1","repo1","http://example.com", Some("key")),Set(WebHook.PullRequest))))
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == List((WebHook("user1","repo1","http://example.com")))) assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == List((WebHook("user1","repo1","http://example.com", Some("key")))))
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.Push) == Nil) assert(service.getWebHooksByEvent("user1", "repo1", WebHook.Push) == Nil)
assert(service.getWebHook("user1", "repo1", "http://example.com2") == None) assert(service.getWebHook("user1", "repo1", "http://example.com2") == None)
assert(service.getWebHook("user2", "repo1", "http://example.com") == None) assert(service.getWebHook("user2", "repo1", "http://example.com") == None)
assert(service.getWebHook("user1", "repo2", "http://example.com") == None) assert(service.getWebHook("user1", "repo2", "http://example.com") == None)
service.updateWebHook("user1", "repo1", "http://example.com", Set(WebHook.Push, WebHook.Issues)) service.updateWebHook("user1", "repo1", "http://example.com", Set(WebHook.Push, WebHook.Issues), Some("key"))
assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((WebHook("user1","repo1","http://example.com"),Set(WebHook.Push, WebHook.Issues)))) assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((WebHook("user1","repo1","http://example.com", Some("key")),Set(WebHook.Push, WebHook.Issues))))
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == Nil) assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == Nil)
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.Push) == List((WebHook("user1","repo1","http://example.com")))) assert(service.getWebHooksByEvent("user1", "repo1", WebHook.Push) == List((WebHook("user1","repo1","http://example.com", Some("key")))))
service.deleteWebHook("user1", "repo1", "http://example.com") service.deleteWebHook("user1", "repo1", "http://example.com")
assert(service.getWebHook("user1", "repo1", "http://example.com") == None) assert(service.getWebHook("user1", "repo1", "http://example.com") == None)
} } } }
test("getWebHooks, getWebHooksByEvent") { withTestDB { implicit session => test("getWebHooks, getWebHooksByEvent") { withTestDB { implicit session =>
val user1 = generateNewUserWithDBRepository("user1","repo1") val user1 = generateNewUserWithDBRepository("user1","repo1")
service.addWebHook("user1", "repo1", "http://example.com/1", Set(WebHook.PullRequest)) service.addWebHook("user1", "repo1", "http://example.com/1", Set(WebHook.PullRequest), Some("key"))
service.addWebHook("user1", "repo1", "http://example.com/2", Set(WebHook.Push)) service.addWebHook("user1", "repo1", "http://example.com/2", Set(WebHook.Push), Some("key"))
service.addWebHook("user1", "repo1", "http://example.com/3", Set(WebHook.PullRequest,WebHook.Push)) service.addWebHook("user1", "repo1", "http://example.com/3", Set(WebHook.PullRequest,WebHook.Push), Some("key"))
assert(service.getWebHooks("user1", "repo1") == List( assert(service.getWebHooks("user1", "repo1") == List(
WebHook("user1","repo1","http://example.com/1")->Set(WebHook.PullRequest), WebHook("user1","repo1","http://example.com/1", Some("key"))->Set(WebHook.PullRequest),
WebHook("user1","repo1","http://example.com/2")->Set(WebHook.Push), WebHook("user1","repo1","http://example.com/2", Some("key"))->Set(WebHook.Push),
WebHook("user1","repo1","http://example.com/3")->Set(WebHook.PullRequest,WebHook.Push))) WebHook("user1","repo1","http://example.com/3", Some("key"))->Set(WebHook.PullRequest,WebHook.Push)))
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == List( assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == List(
WebHook("user1","repo1","http://example.com/1"), WebHook("user1","repo1","http://example.com/1", Some("key")),
WebHook("user1","repo1","http://example.com/3"))) WebHook("user1","repo1","http://example.com/3", Some("key"))))
} } } }
} }

View File

@@ -84,14 +84,14 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
fullName = "user@localhost", fullName = "user@localhost",
mailAddress = "", mailAddress = "",
password = "", password = "",
isAdmin = false, administrator = false,
url = None, url = None,
registeredDate = new Date(), registeredDate = new Date(),
updatedDate = new Date(), updatedDate = new Date(),
lastLoginDate = None, lastLoginDate = None,
image = image, image = image,
isGroupAccount = false, groupAccount = false,
isRemoved = false) removed = false)
private def createSystemSettings(useGravatar: Boolean) = private def createSystemSettings(useGravatar: Boolean) =
SystemSettings( SystemSettings(