mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-05 04:56:02 +01:00
Merge pull request #2163 from kounoike/pr-refactor-apicontroller
Split ApiController
This commit is contained in:
@@ -1,29 +1,17 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.api._
|
import gitbucket.core.api._
|
||||||
import gitbucket.core.model._
|
import gitbucket.core.controller.api.{ApiOrganizationControllerBase, ApiRepositoryControllerBase, ApiUserControllerBase}
|
||||||
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
|
||||||
import gitbucket.core.service.PullRequestService._
|
|
||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.Directory._
|
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.JGitUtil._
|
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
import gitbucket.core.servlet.Database
|
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
|
||||||
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import org.eclipse.jgit.revwalk.RevWalk
|
|
||||||
import org.scalatra.{Created, NoContent, UnprocessableEntity}
|
|
||||||
|
|
||||||
import scala.collection.JavaConverters._
|
|
||||||
import scala.concurrent.Await
|
|
||||||
import scala.concurrent.duration.Duration
|
|
||||||
|
|
||||||
class ApiController
|
class ApiController
|
||||||
extends ApiControllerBase
|
extends ApiControllerBase
|
||||||
|
with ApiOrganizationControllerBase
|
||||||
|
with ApiRepositoryControllerBase
|
||||||
|
with ApiUserControllerBase
|
||||||
with RepositoryService
|
with RepositoryService
|
||||||
with AccountService
|
with AccountService
|
||||||
with ProtectedBranchService
|
with ProtectedBranchService
|
||||||
@@ -50,25 +38,6 @@ class ApiController
|
|||||||
with WritableUsersAuthenticator
|
with WritableUsersAuthenticator
|
||||||
|
|
||||||
trait ApiControllerBase extends ControllerBase {
|
trait ApiControllerBase extends ControllerBase {
|
||||||
self: RepositoryService
|
|
||||||
with AccountService
|
|
||||||
with ProtectedBranchService
|
|
||||||
with IssuesService
|
|
||||||
with LabelsService
|
|
||||||
with MilestonesService
|
|
||||||
with PullRequestService
|
|
||||||
with CommitsService
|
|
||||||
with CommitStatusService
|
|
||||||
with RepositoryCreationService
|
|
||||||
with IssueCreationService
|
|
||||||
with HandleCommentService
|
|
||||||
with PrioritiesService
|
|
||||||
with OwnerAuthenticator
|
|
||||||
with UsersAuthenticator
|
|
||||||
with GroupManagerAuthenticator
|
|
||||||
with ReferrerAuthenticator
|
|
||||||
with ReadableUsersAuthenticator
|
|
||||||
with WritableUsersAuthenticator =>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 404 for non-implemented api
|
* 404 for non-implemented api
|
||||||
@@ -76,6 +45,18 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
get("/api/v3/*") {
|
get("/api/v3/*") {
|
||||||
NotFound()
|
NotFound()
|
||||||
}
|
}
|
||||||
|
post("/api/v3/*") {
|
||||||
|
NotFound()
|
||||||
|
}
|
||||||
|
put("/api/v3/*") {
|
||||||
|
NotFound()
|
||||||
|
}
|
||||||
|
delete("/api/v3/*") {
|
||||||
|
NotFound()
|
||||||
|
}
|
||||||
|
patch("/api/v3/*") {
|
||||||
|
NotFound()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://developer.github.com/v3/#root-endpoint
|
* https://developer.github.com/v3/#root-endpoint
|
||||||
@@ -84,318 +65,6 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
JsonFormat(ApiEndPoint())
|
JsonFormat(ApiEndPoint())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/orgs/#get-an-organization
|
|
||||||
*/
|
|
||||||
get("/api/v3/orgs/:groupName") {
|
|
||||||
getAccountByUserName(params("groupName")).filter(account => account.isGroupAccount).map { account =>
|
|
||||||
JsonFormat(ApiUser(account))
|
|
||||||
} getOrElse NotFound()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/users/#get-a-single-user
|
|
||||||
* This API also returns group information (as GitHub).
|
|
||||||
*/
|
|
||||||
get("/api/v3/users/:userName") {
|
|
||||||
getAccountByUserName(params("userName")).map { account =>
|
|
||||||
JsonFormat(ApiUser(account))
|
|
||||||
} getOrElse NotFound()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/#list-organization-repositories
|
|
||||||
*/
|
|
||||||
get("/api/v3/orgs/:orgName/repos") {
|
|
||||||
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map { r =>
|
|
||||||
ApiRepository(r, getAccountByUserName(r.owner).get)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/#list-user-repositories
|
|
||||||
*/
|
|
||||||
get("/api/v3/users/:userName/repos") {
|
|
||||||
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map { r =>
|
|
||||||
ApiRepository(r, getAccountByUserName(r.owner).get)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* https://developer.github.com/v3/repos/branches/#list-branches
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/branches")(referrersOnly { repository =>
|
|
||||||
JsonFormat(
|
|
||||||
JGitUtil
|
|
||||||
.getBranches(
|
|
||||||
owner = repository.owner,
|
|
||||||
name = repository.name,
|
|
||||||
defaultBranch = repository.repository.defaultBranch,
|
|
||||||
origin = repository.repository.originUserName.isEmpty
|
|
||||||
)
|
|
||||||
.map { br =>
|
|
||||||
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/branches/#get-branch
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/branches/*")(referrersOnly { repository =>
|
|
||||||
//import gitbucket.core.api._
|
|
||||||
(for {
|
|
||||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
|
||||||
br <- getBranches(
|
|
||||||
repository.owner,
|
|
||||||
repository.name,
|
|
||||||
repository.repository.defaultBranch,
|
|
||||||
repository.repository.originUserName.isEmpty
|
|
||||||
).find(_.name == branch)
|
|
||||||
} yield {
|
|
||||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
|
||||||
JsonFormat(
|
|
||||||
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
|
|
||||||
)
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
* https://developer.github.com/v3/repos/contents/#get-contents
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/contents")(referrersOnly { repository =>
|
|
||||||
getContents(repository, ".", params.getOrElse("ref", repository.repository.defaultBranch))
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
* https://developer.github.com/v3/repos/contents/#get-contents
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/contents/*")(referrersOnly { repository =>
|
|
||||||
getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch))
|
|
||||||
})
|
|
||||||
|
|
||||||
private def getContents(repository: RepositoryService.RepositoryInfo, path: String, refStr: String) = {
|
|
||||||
def getFileInfo(git: Git, revision: String, pathStr: String): Option[FileInfo] = {
|
|
||||||
val (dirName, fileName) = pathStr.lastIndexOf('/') match {
|
|
||||||
case -1 =>
|
|
||||||
(".", pathStr)
|
|
||||||
case n =>
|
|
||||||
(pathStr.take(n), pathStr.drop(n + 1))
|
|
||||||
}
|
|
||||||
getFileList(git, revision, dirName).find(f => f.name.equals(fileName))
|
|
||||||
}
|
|
||||||
|
|
||||||
using(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
|
||||||
val fileList = getFileList(git, refStr, path)
|
|
||||||
if (fileList.isEmpty) { // file or NotFound
|
|
||||||
getFileInfo(git, refStr, path)
|
|
||||||
.flatMap { f =>
|
|
||||||
val largeFile = params.get("large_file").exists(s => s.equals("true"))
|
|
||||||
val content = getContentFromId(git, f.id, largeFile)
|
|
||||||
request.getHeader("Accept") match {
|
|
||||||
case "application/vnd.github.v3.raw" => {
|
|
||||||
contentType = "application/vnd.github.v3.raw"
|
|
||||||
content
|
|
||||||
}
|
|
||||||
case "application/vnd.github.v3.html" if isRenderable(f.name) => {
|
|
||||||
contentType = "application/vnd.github.v3.html"
|
|
||||||
content.map { c =>
|
|
||||||
List(
|
|
||||||
"<div data-path=\"",
|
|
||||||
path,
|
|
||||||
"\" id=\"file\">",
|
|
||||||
"<article>",
|
|
||||||
renderMarkup(path.split("/").toList, new String(c), refStr, repository, false, false, true).body,
|
|
||||||
"</article>",
|
|
||||||
"</div>"
|
|
||||||
).mkString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "application/vnd.github.v3.html" => {
|
|
||||||
contentType = "application/vnd.github.v3.html"
|
|
||||||
content.map { c =>
|
|
||||||
List(
|
|
||||||
"<div data-path=\"",
|
|
||||||
path,
|
|
||||||
"\" id=\"file\">",
|
|
||||||
"<div class=\"plain\">",
|
|
||||||
"<pre>",
|
|
||||||
play.twirl.api.HtmlFormat.escape(new String(c)).body,
|
|
||||||
"</pre>",
|
|
||||||
"</div>",
|
|
||||||
"</div>"
|
|
||||||
).mkString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case _ =>
|
|
||||||
Some(JsonFormat(ApiContents(f, RepositoryName(repository), content)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.getOrElse(NotFound())
|
|
||||||
|
|
||||||
} else { // directory
|
|
||||||
JsonFormat(fileList.map { f =>
|
|
||||||
ApiContents(f, RepositoryName(repository), None)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* https://developer.github.com/v3/git/refs/#get-a-reference
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { repository =>
|
|
||||||
val revstr = multiParams("splat").head
|
|
||||||
using(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
|
||||||
val ref = git.getRepository().findRef(revstr)
|
|
||||||
|
|
||||||
if (ref != null) {
|
|
||||||
val sha = ref.getObjectId().name()
|
|
||||||
JsonFormat(ApiRef(revstr, ApiObject(sha)))
|
|
||||||
|
|
||||||
} else {
|
|
||||||
val refs = git
|
|
||||||
.getRepository()
|
|
||||||
.getRefDatabase()
|
|
||||||
.getRefsByPrefix("refs/")
|
|
||||||
.asScala
|
|
||||||
|
|
||||||
JsonFormat(refs.map { ref =>
|
|
||||||
val sha = ref.getObjectId().name()
|
|
||||||
ApiRef(revstr, ApiObject(sha))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/collaborators/#list-collaborators
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/collaborators")(referrersOnly { repository =>
|
|
||||||
// TODO Should ApiUser take permission? getCollaboratorUserNames does not return owner group members.
|
|
||||||
JsonFormat(
|
|
||||||
getCollaboratorUserNames(params("owner"), params("repository")).map(u => ApiUser(getAccountByUserName(u).get))
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/users/#get-the-authenticated-user
|
|
||||||
*/
|
|
||||||
get("/api/v3/user") {
|
|
||||||
context.loginAccount.map { account =>
|
|
||||||
JsonFormat(ApiUser(account))
|
|
||||||
} getOrElse Unauthorized()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List user's own repository
|
|
||||||
* https://developer.github.com/v3/repos/#list-your-repositories
|
|
||||||
*/
|
|
||||||
get("/api/v3/user/repos")(usersOnly {
|
|
||||||
JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map { r =>
|
|
||||||
ApiRepository(r, getAccountByUserName(r.owner).get)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
val f = createRepository(
|
|
||||||
context.loginAccount.get,
|
|
||||||
owner,
|
|
||||||
data.name,
|
|
||||||
data.description,
|
|
||||||
data.`private`,
|
|
||||||
data.auto_init
|
|
||||||
)
|
|
||||||
Await.result(f, Duration.Inf)
|
|
||||||
|
|
||||||
val repository = Database() withTransaction { session =>
|
|
||||||
getRepository(owner, data.name)(session).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) {
|
|
||||||
val f = createRepository(
|
|
||||||
context.loginAccount.get,
|
|
||||||
groupName,
|
|
||||||
data.name,
|
|
||||||
data.description,
|
|
||||||
data.`private`,
|
|
||||||
data.auto_init
|
|
||||||
)
|
|
||||||
Await.result(f, Duration.Inf)
|
|
||||||
val repository = Database() withTransaction { session =>
|
|
||||||
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/:repository/branches/*")(ownerOnly { repository =>
|
|
||||||
import gitbucket.core.api._
|
|
||||||
(for {
|
|
||||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
|
||||||
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
|
||||||
br <- getBranches(
|
|
||||||
repository.owner,
|
|
||||||
repository.name,
|
|
||||||
repository.repository.defaultBranch,
|
|
||||||
repository.repository.originUserName.isEmpty
|
|
||||||
).find(_.name == branch)
|
|
||||||
} 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, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status
|
* @see https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status
|
||||||
* but not enabled.
|
* but not enabled.
|
||||||
@@ -406,459 +75,6 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
|
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/issues/#list-issues-for-a-repository
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/issues")(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)] =
|
|
||||||
searchIssueByApi(
|
|
||||||
condition = condition,
|
|
||||||
offset = (page - 1) * PullRequestLimit,
|
|
||||||
limit = PullRequestLimit,
|
|
||||||
repos = repository.owner -> repository.name
|
|
||||||
)
|
|
||||||
|
|
||||||
JsonFormat(issues.map {
|
|
||||||
case (issue, issueUser) =>
|
|
||||||
ApiIssue(
|
|
||||||
issue = issue,
|
|
||||||
repositoryName = RepositoryName(repository),
|
|
||||||
user = ApiUser(issueUser),
|
|
||||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
|
||||||
.map(ApiLabel(_, RepositoryName(repository)))
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/issues/#get-a-single-issue
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
|
||||||
(for {
|
|
||||||
issueId <- params("id").toIntOpt
|
|
||||||
issue <- getIssue(repository.owner, repository.name, issueId.toString)
|
|
||||||
openedUser <- getAccountByUserName(issue.openedUserName)
|
|
||||||
} yield {
|
|
||||||
JsonFormat(
|
|
||||||
ApiIssue(
|
|
||||||
issue,
|
|
||||||
RepositoryName(repository),
|
|
||||||
ApiUser(openedUser),
|
|
||||||
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository)))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/issues/#create-an-issue
|
|
||||||
*/
|
|
||||||
post("/api/v3/repos/:owner/:repository/issues")(readableUsersOnly { repository =>
|
|
||||||
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
|
|
||||||
(for {
|
|
||||||
data <- extractFromJsonBody[CreateAnIssue]
|
|
||||||
loginAccount <- context.loginAccount
|
|
||||||
} yield {
|
|
||||||
val milestone = data.milestone.flatMap(getMilestone(repository.owner, repository.name, _))
|
|
||||||
val issue = createIssue(
|
|
||||||
repository,
|
|
||||||
data.title,
|
|
||||||
data.body,
|
|
||||||
data.assignees.headOption,
|
|
||||||
milestone.map(_.milestoneId),
|
|
||||||
None,
|
|
||||||
data.labels,
|
|
||||||
loginAccount
|
|
||||||
)
|
|
||||||
JsonFormat(
|
|
||||||
ApiIssue(
|
|
||||||
issue,
|
|
||||||
RepositoryName(repository),
|
|
||||||
ApiUser(loginAccount),
|
|
||||||
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
|
||||||
.map(ApiLabel(_, RepositoryName(repository)))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}) getOrElse NotFound()
|
|
||||||
} else Unauthorized()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
} 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")(writableUsersOnly { 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")(writableUsersOnly { 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")(writableUsersOnly { 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, Option[Account])] =
|
|
||||||
searchPullRequestByApi(
|
|
||||||
condition = condition,
|
|
||||||
offset = (page - 1) * PullRequestLimit,
|
|
||||||
limit = PullRequestLimit,
|
|
||||||
repos = repository.owner -> repository.name
|
|
||||||
)
|
|
||||||
|
|
||||||
JsonFormat(issues.map {
|
|
||||||
case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignee) =>
|
|
||||||
ApiPullRequest(
|
|
||||||
issue = issue,
|
|
||||||
pullRequest = pullRequest,
|
|
||||||
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
|
||||||
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
|
||||||
user = ApiUser(issueUser),
|
|
||||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
|
||||||
.map(ApiLabel(_, RepositoryName(repository))),
|
|
||||||
assignee = assignee.map(ApiUser.apply),
|
|
||||||
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.empty
|
|
||||||
)
|
|
||||||
baseOwner <- users.get(repository.owner)
|
|
||||||
headOwner <- users.get(pullRequest.requestUserName)
|
|
||||||
issueUser <- users.get(issue.openedUserName)
|
|
||||||
assignee = issue.assignedUserName.flatMap { userName =>
|
|
||||||
getAccountByUserName(userName, false)
|
|
||||||
}
|
|
||||||
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
|
|
||||||
} yield {
|
|
||||||
JsonFormat(
|
|
||||||
ApiPullRequest(
|
|
||||||
issue = issue,
|
|
||||||
pullRequest = pullRequest,
|
|
||||||
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
|
||||||
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
|
||||||
user = ApiUser(issueUser),
|
|
||||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
|
||||||
.map(ApiLabel(_, RepositoryName(repository))),
|
|
||||||
assignee = assignee.map(ApiUser.apply),
|
|
||||||
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}) 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/:repository/statuses/:sha")(writableUsersOnly { 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/:repository/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/:repository/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/:repository/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()
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://developer.github.com/v3/repos/commits/#get-a-single-commit
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/commits/:sha")(referrersOnly { repository =>
|
|
||||||
val owner = repository.owner
|
|
||||||
val name = repository.name
|
|
||||||
val sha = params("sha")
|
|
||||||
|
|
||||||
using(Git.open(getRepositoryDir(owner, name))) {
|
|
||||||
git =>
|
|
||||||
val repo = git.getRepository
|
|
||||||
val objectId = repo.resolve(sha)
|
|
||||||
val commitInfo = using(new RevWalk(repo)) { revWalk =>
|
|
||||||
new CommitInfo(revWalk.parseCommit(objectId))
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonFormat(
|
|
||||||
ApiCommits(
|
|
||||||
repositoryName = RepositoryName(repository),
|
|
||||||
commitInfo = commitInfo,
|
|
||||||
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
|
|
||||||
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
|
|
||||||
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
|
|
||||||
commentCount = getCommitComment(repository.owner, repository.name, sha).size
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
private def getAccount(userName: String, email: String): Account = {
|
|
||||||
getAccountByMailAddress(email).getOrElse {
|
|
||||||
Account(
|
|
||||||
userName = userName,
|
|
||||||
fullName = userName,
|
|
||||||
mailAddress = email,
|
|
||||||
password = "xxx",
|
|
||||||
isAdmin = false,
|
|
||||||
url = None,
|
|
||||||
registeredDate = new java.util.Date(),
|
|
||||||
updatedDate = new java.util.Date(),
|
|
||||||
lastLoginDate = None,
|
|
||||||
image = None,
|
|
||||||
isGroupAccount = false,
|
|
||||||
isRemoved = true,
|
|
||||||
description = None
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
|
||||||
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
|
||||||
|
|
||||||
/**
|
|
||||||
* non-GitHub compatible API for Jenkins-Plugin
|
|
||||||
*/
|
|
||||||
get("/api/v3/repos/:owner/:repository/raw/*")(referrersOnly { repository =>
|
|
||||||
val (id, path) = repository.splitPath(multiParams("splat").head)
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
|
||||||
|
|
||||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
|
||||||
responseRawFile(git, objectId, path, repository)
|
|
||||||
} getOrElse NotFound()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* non-GitHub compatible API for listing plugins
|
* non-GitHub compatible API for listing plugins
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{ApiObject, ApiRef, JsonFormat}
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.util.Directory.getRepositoryDir
|
||||||
|
import gitbucket.core.util.ReferrerAuthenticator
|
||||||
|
import gitbucket.core.util.SyntaxSugars.using
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
|
trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||||
|
self: ReferrerAuthenticator =>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i. Get a reference
|
||||||
|
* https://developer.github.com/v3/git/refs/#get-a-reference
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { repository =>
|
||||||
|
val revstr = multiParams("splat").head
|
||||||
|
using(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||||
|
val ref = git.getRepository().findRef(revstr)
|
||||||
|
|
||||||
|
if (ref != null) {
|
||||||
|
val sha = ref.getObjectId().name()
|
||||||
|
JsonFormat(ApiRef(revstr, ApiObject(sha)))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
val refs = git
|
||||||
|
.getRepository()
|
||||||
|
.getRefDatabase()
|
||||||
|
.getRefsByPrefix("refs/")
|
||||||
|
.asScala
|
||||||
|
|
||||||
|
JsonFormat(refs.map { ref =>
|
||||||
|
val sha = ref.getObjectId().name()
|
||||||
|
ApiRef(revstr, ApiObject(sha))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
* ii. Get all references
|
||||||
|
* https://developer.github.com/v3/git/refs/#get-all-references
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Create a reference
|
||||||
|
* https://developer.github.com/v3/git/refs/#create-a-reference
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Update a reference
|
||||||
|
* https://developer.github.com/v3/git/refs/#update-a-reference
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Delete a reference
|
||||||
|
* https://developer.github.com/v3/git/refs/#delete-a-reference
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{ApiComment, ApiUser, CreateAComment, JsonFormat}
|
||||||
|
import gitbucket.core.controller.{Context, ControllerBase}
|
||||||
|
import gitbucket.core.service._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName}
|
||||||
|
|
||||||
|
trait ApiIssueCommentControllerBase extends ControllerBase {
|
||||||
|
self: AccountService
|
||||||
|
with IssuesService
|
||||||
|
with RepositoryService
|
||||||
|
with HandleCommentService
|
||||||
|
with MilestonesService
|
||||||
|
with ReadableUsersAuthenticator
|
||||||
|
with ReferrerAuthenticator =>
|
||||||
|
/*
|
||||||
|
* i. List comments on an issue
|
||||||
|
* 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)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(comments.map {
|
||||||
|
case (issueComment, user, issue) =>
|
||||||
|
ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest)
|
||||||
|
})
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ii. List comments in a repository
|
||||||
|
* https://developer.github.com/v3/issues/comments/#list-comments-in-a-repository
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Get a single comment
|
||||||
|
* https://developer.github.com/v3/issues/comments/#get-a-single-comment
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Create a comment
|
||||||
|
* 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()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Edit a comment
|
||||||
|
* https://developer.github.com/v3/issues/comments/#edit-a-comment
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vi. Delete a comment
|
||||||
|
* https://developer.github.com/v3/issues/comments/#delete-a-comment
|
||||||
|
*/
|
||||||
|
|
||||||
|
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
||||||
|
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api._
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.model.{Account, Issue}
|
||||||
|
import gitbucket.core.service.{AccountService, IssueCreationService, IssuesService, MilestonesService}
|
||||||
|
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||||
|
import gitbucket.core.service.PullRequestService.PullRequestLimit
|
||||||
|
import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName}
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
|
||||||
|
trait ApiIssueControllerBase extends ControllerBase {
|
||||||
|
self: AccountService
|
||||||
|
with IssuesService
|
||||||
|
with IssueCreationService
|
||||||
|
with MilestonesService
|
||||||
|
with ReadableUsersAuthenticator
|
||||||
|
with ReferrerAuthenticator =>
|
||||||
|
/*
|
||||||
|
* i. List issues
|
||||||
|
* https://developer.github.com/v3/issues/#list-issues
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ii. List issues for a repository
|
||||||
|
* https://developer.github.com/v3/issues/#list-issues-for-a-repository
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/issues")(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)] =
|
||||||
|
searchIssueByApi(
|
||||||
|
condition = condition,
|
||||||
|
offset = (page - 1) * PullRequestLimit,
|
||||||
|
limit = PullRequestLimit,
|
||||||
|
repos = repository.owner -> repository.name
|
||||||
|
)
|
||||||
|
|
||||||
|
JsonFormat(issues.map {
|
||||||
|
case (issue, issueUser) =>
|
||||||
|
ApiIssue(
|
||||||
|
issue = issue,
|
||||||
|
repositoryName = RepositoryName(repository),
|
||||||
|
user = ApiUser(issueUser),
|
||||||
|
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||||
|
.map(ApiLabel(_, RepositoryName(repository)))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Get a single issue
|
||||||
|
* https://developer.github.com/v3/issues/#get-a-single-issue
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||||
|
(for {
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
issue <- getIssue(repository.owner, repository.name, issueId.toString)
|
||||||
|
openedUser <- getAccountByUserName(issue.openedUserName)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(
|
||||||
|
ApiIssue(
|
||||||
|
issue,
|
||||||
|
RepositoryName(repository),
|
||||||
|
ApiUser(openedUser),
|
||||||
|
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Create an issue
|
||||||
|
* https://developer.github.com/v3/issues/#create-an-issue
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/issues")(readableUsersOnly { repository =>
|
||||||
|
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
|
||||||
|
(for {
|
||||||
|
data <- extractFromJsonBody[CreateAnIssue]
|
||||||
|
loginAccount <- context.loginAccount
|
||||||
|
} yield {
|
||||||
|
val milestone = data.milestone.flatMap(getMilestone(repository.owner, repository.name, _))
|
||||||
|
val issue = createIssue(
|
||||||
|
repository,
|
||||||
|
data.title,
|
||||||
|
data.body,
|
||||||
|
data.assignees.headOption,
|
||||||
|
milestone.map(_.milestoneId),
|
||||||
|
None,
|
||||||
|
data.labels,
|
||||||
|
loginAccount
|
||||||
|
)
|
||||||
|
JsonFormat(
|
||||||
|
ApiIssue(
|
||||||
|
issue,
|
||||||
|
RepositoryName(repository),
|
||||||
|
ApiUser(loginAccount),
|
||||||
|
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||||
|
.map(ApiLabel(_, RepositoryName(repository)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
} else Unauthorized()
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
* v. Edit an issue
|
||||||
|
* https://developer.github.com/v3/issues/#edit-an-issue
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vi. Lock an issue
|
||||||
|
* https://developer.github.com/v3/issues/#lock-an-issue
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vii. Unlock an issue
|
||||||
|
* https://developer.github.com/v3/issues/#unlock-an-issue
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{ApiError, ApiLabel, CreateALabel, JsonFormat}
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.service._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util._
|
||||||
|
import org.scalatra.{Created, NoContent, UnprocessableEntity}
|
||||||
|
|
||||||
|
trait ApiIssueLabelControllerBase extends ControllerBase {
|
||||||
|
self: AccountService
|
||||||
|
with IssuesService
|
||||||
|
with LabelsService
|
||||||
|
with ReferrerAuthenticator
|
||||||
|
with WritableUsersAuthenticator =>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i. 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))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ii. 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()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Create a label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#create-a-label
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/labels")(writableUsersOnly { 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()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Update a label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#update-a-label
|
||||||
|
*/
|
||||||
|
patch("/api/v3/repos/:owner/:repository/labels/:labelName")(writableUsersOnly { 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()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Delete a label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#delete-a-label
|
||||||
|
*/
|
||||||
|
delete("/api/v3/repos/:owner/:repository/labels/:labelName")(writableUsersOnly { 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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
* vi. List labels on an issue
|
||||||
|
* https://developer.github.com/v3/issues/labels/#list-labels-on-an-issue
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vii. Add labels to an issue
|
||||||
|
* https://developer.github.com/v3/issues/labels/#add-labels-to-an-issue
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* viii. Remove a label from an issue
|
||||||
|
* https://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ix. Replace all labels for an issue
|
||||||
|
* https://developer.github.com/v3/issues/labels/#replace-all-labels-for-an-issue
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* x. Remove all labels from an issue
|
||||||
|
* https://developer.github.com/v3/issues/labels/#remove-all-labels-from-an-issue
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xi Get labels for every issue in a milestone
|
||||||
|
* https://developer.github.com/v3/issues/labels/#get-labels-for-every-issue-in-a-milestone
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{ApiRepository, ApiUser, JsonFormat}
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
|
||||||
|
trait ApiOrganizationControllerBase extends ControllerBase {
|
||||||
|
self: RepositoryService with AccountService =>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i. List your organizations
|
||||||
|
* https://developer.github.com/v3/orgs/#list-your-organizations
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ii. List all organizations
|
||||||
|
* https://developer.github.com/v3/orgs/#list-all-organizations
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. List user organizations
|
||||||
|
* https://developer.github.com/v3/orgs/#list-user-organizations
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iv. Get an organization
|
||||||
|
* https://developer.github.com/v3/orgs/#get-an-organization
|
||||||
|
*/
|
||||||
|
get("/api/v3/orgs/:groupName") {
|
||||||
|
getAccountByUserName(params("groupName")).filter(account => account.isGroupAccount).map { account =>
|
||||||
|
JsonFormat(ApiUser(account))
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api._
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.model.{Account, Issue, PullRequest, Repository}
|
||||||
|
import gitbucket.core.service.{AccountService, IssuesService, PullRequestService, RepositoryService}
|
||||||
|
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||||
|
import gitbucket.core.service.PullRequestService.PullRequestLimit
|
||||||
|
import gitbucket.core.util.Directory.getRepositoryDir
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||||
|
import gitbucket.core.util.SyntaxSugars.using
|
||||||
|
import gitbucket.core.util.{ReferrerAuthenticator, RepositoryName}
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
|
trait ApiPullRequestControllerBase extends ControllerBase {
|
||||||
|
self: AccountService with IssuesService with PullRequestService with RepositoryService with ReferrerAuthenticator =>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i. Link Relations
|
||||||
|
* https://developer.github.com/v3/pulls/#link-relations
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ii. List pull requests
|
||||||
|
* https://developer.github.com/v3/pulls/#list-pull-requests
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 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, Option[Account])] =
|
||||||
|
searchPullRequestByApi(
|
||||||
|
condition = condition,
|
||||||
|
offset = (page - 1) * PullRequestLimit,
|
||||||
|
limit = PullRequestLimit,
|
||||||
|
repos = repository.owner -> repository.name
|
||||||
|
)
|
||||||
|
|
||||||
|
JsonFormat(issues.map {
|
||||||
|
case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignee) =>
|
||||||
|
ApiPullRequest(
|
||||||
|
issue = issue,
|
||||||
|
pullRequest = pullRequest,
|
||||||
|
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
||||||
|
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
||||||
|
user = ApiUser(issueUser),
|
||||||
|
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||||
|
.map(ApiLabel(_, RepositoryName(repository))),
|
||||||
|
assignee = assignee.map(ApiUser.apply),
|
||||||
|
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Get a single pull request
|
||||||
|
* https://developer.github.com/v3/pulls/#get-a-single-pull-request
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 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.empty
|
||||||
|
)
|
||||||
|
baseOwner <- users.get(repository.owner)
|
||||||
|
headOwner <- users.get(pullRequest.requestUserName)
|
||||||
|
issueUser <- users.get(issue.openedUserName)
|
||||||
|
assignee = issue.assignedUserName.flatMap { userName =>
|
||||||
|
getAccountByUserName(userName, false)
|
||||||
|
}
|
||||||
|
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(
|
||||||
|
ApiPullRequest(
|
||||||
|
issue = issue,
|
||||||
|
pullRequest = pullRequest,
|
||||||
|
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
|
||||||
|
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
|
||||||
|
user = ApiUser(issueUser),
|
||||||
|
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||||
|
.map(ApiLabel(_, RepositoryName(repository))),
|
||||||
|
assignee = assignee.map(ApiUser.apply),
|
||||||
|
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Create a pull request
|
||||||
|
* https://developer.github.com/v3/pulls/#create-a-pull-request
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Update a pull request
|
||||||
|
* https://developer.github.com/v3/pulls/#update-a-pull-request
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vi. List commits on a pull request
|
||||||
|
* https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 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()
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
* vii. List pull requests files
|
||||||
|
* https://developer.github.com/v3/pulls/#list-pull-requests-files
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* viii. Get if a pull request has been merged
|
||||||
|
* https://developer.github.com/v3/pulls/#get-if-a-pull-request-has-been-merged
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ix. Merge a pull request (Merge Button)
|
||||||
|
* https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* x. Labels, assignees, and milestones
|
||||||
|
* https://developer.github.com/v3/pulls/#labels-assignees-and-milestones
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api._
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.service.{AccountService, ProtectedBranchService, RepositoryService}
|
||||||
|
import gitbucket.core.util._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.JGitUtil.getBranches
|
||||||
|
|
||||||
|
trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||||
|
self: RepositoryService
|
||||||
|
with AccountService
|
||||||
|
with OwnerAuthenticator
|
||||||
|
with UsersAuthenticator
|
||||||
|
with GroupManagerAuthenticator
|
||||||
|
with ProtectedBranchService
|
||||||
|
with ReferrerAuthenticator
|
||||||
|
with ReadableUsersAuthenticator
|
||||||
|
with WritableUsersAuthenticator =>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* i. List branches
|
||||||
|
* https://developer.github.com/v3/repos/branches/#list-branches
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/branches")(referrersOnly { repository =>
|
||||||
|
JsonFormat(
|
||||||
|
JGitUtil
|
||||||
|
.getBranches(
|
||||||
|
owner = repository.owner,
|
||||||
|
name = repository.name,
|
||||||
|
defaultBranch = repository.repository.defaultBranch,
|
||||||
|
origin = repository.repository.originUserName.isEmpty
|
||||||
|
)
|
||||||
|
.map { br =>
|
||||||
|
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ii. Get branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#get-branch
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/branches/*")(referrersOnly { repository =>
|
||||||
|
//import gitbucket.core.api._
|
||||||
|
(for {
|
||||||
|
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||||
|
br <- getBranches(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
repository.repository.defaultBranch,
|
||||||
|
repository.repository.originUserName.isEmpty
|
||||||
|
).find(_.name == branch)
|
||||||
|
} yield {
|
||||||
|
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||||
|
JsonFormat(
|
||||||
|
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
|
||||||
|
)
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Get branch protection
|
||||||
|
* https://developer.github.com/v3/repos/branches/#get-branch-protection
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Update branch protection
|
||||||
|
* https://developer.github.com/v3/repos/branches/#update-branch-protection
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Remove branch protection
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-branch-protection
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vi. Get required status checks of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#get-required-status-checks-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vii. Update required status checks of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#update-required-status-checks-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* viii. Remove required status checks of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-required-status-checks-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ix. List required status checks contexts of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#list-required-status-checks-contexts-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* x. Replace required status checks contexts of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#replace-required-status-checks-contexts-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xi. Add required status checks contexts of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-required-status-checks-contexts-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xii. Remove required status checks contexts of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-required-status-checks-contexts-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xiii. Get pull request review enforcement of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#get-pull-request-review-enforcement-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xiv. Update pull request review enforcement of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#update-pull-request-review-enforcement-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xv. Remove pull request review enforcement of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-pull-request-review-enforcement-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xvi. Get required signatures of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#get-required-signatures-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xvii. Add required signatures of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#add-required-signatures-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xviii. Remove required signatures of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-required-signatures-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xix. Get admin enforcement of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#get-admin-enforcement-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xx. Add admin enforcement of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#add-admin-enforcement-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxi. Remove admin enforcement of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-admin-enforcement-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxii. Get restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#get-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxiii. Remove restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxiv. List team restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#list-team-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxv. Replace team restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#replace-team-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxvi. Add team restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#add-team-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxvii. Remove team restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-team-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxviii. List user restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#list-user-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxix. Replace user restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#replace-user-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxx. Add user restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#add-user-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xxxi. Remove user restrictions of protected branch
|
||||||
|
* https://developer.github.com/v3/repos/branches/#remove-user-restrictions-of-protected-branch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enabling and disabling branch protection: deprecated?
|
||||||
|
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
||||||
|
*/
|
||||||
|
patch("/api/v3/repos/:owner/:repository/branches/*")(ownerOnly { repository =>
|
||||||
|
import gitbucket.core.api._
|
||||||
|
(for {
|
||||||
|
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||||
|
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||||
|
br <- getBranches(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
repository.repository.defaultBranch,
|
||||||
|
repository.repository.originUserName.isEmpty
|
||||||
|
).find(_.name == branch)
|
||||||
|
} 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, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{ApiUser, JsonFormat}
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.ReferrerAuthenticator
|
||||||
|
|
||||||
|
trait ApiRepositoryCollaboratorControllerBase extends ControllerBase {
|
||||||
|
self: RepositoryService with AccountService with ReferrerAuthenticator =>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i. List collaborators
|
||||||
|
* https://developer.github.com/v3/repos/collaborators/#list-collaborators
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/collaborators")(referrersOnly { repository =>
|
||||||
|
// TODO Should ApiUser take permission? getCollaboratorUserNames does not return owner group members.
|
||||||
|
JsonFormat(
|
||||||
|
getCollaboratorUserNames(params("owner"), params("repository")).map(u => ApiUser(getAccountByUserName(u).get))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
* ii. Check if a user is a collaborator
|
||||||
|
* https://developer.github.com/v3/repos/collaborators/#check-if-a-user-is-a-collaborator
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Review a user's permission level
|
||||||
|
* https://developer.github.com/v3/repos/collaborators/#review-a-users-permission-level
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Add user as a collaborator
|
||||||
|
* https://developer.github.com/v3/repos/collaborators/#add-user-as-a-collaborator
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Remove user as a collaborator
|
||||||
|
* https://developer.github.com/v3/repos/collaborators/#remove-user-as-a-collaborator
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{ApiCommits, JsonFormat}
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.model.Account
|
||||||
|
import gitbucket.core.service.{AccountService, CommitsService}
|
||||||
|
import gitbucket.core.util.Directory.getRepositoryDir
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||||
|
import gitbucket.core.util.{JGitUtil, ReferrerAuthenticator, RepositoryName}
|
||||||
|
import gitbucket.core.util.SyntaxSugars.using
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk
|
||||||
|
|
||||||
|
trait ApiRepositoryCommitControllerBase extends ControllerBase {
|
||||||
|
self: AccountService with CommitsService with ReferrerAuthenticator =>
|
||||||
|
/*
|
||||||
|
* i. List commits on a repository
|
||||||
|
* https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ii. Get a single commit
|
||||||
|
* https://developer.github.com/v3/repos/commits/#get-a-single-commit
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/commits/:sha")(referrersOnly { repository =>
|
||||||
|
val owner = repository.owner
|
||||||
|
val name = repository.name
|
||||||
|
val sha = params("sha")
|
||||||
|
|
||||||
|
using(Git.open(getRepositoryDir(owner, name))) {
|
||||||
|
git =>
|
||||||
|
val repo = git.getRepository
|
||||||
|
val objectId = repo.resolve(sha)
|
||||||
|
val commitInfo = using(new RevWalk(repo)) { revWalk =>
|
||||||
|
new CommitInfo(revWalk.parseCommit(objectId))
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonFormat(
|
||||||
|
ApiCommits(
|
||||||
|
repositoryName = RepositoryName(repository),
|
||||||
|
commitInfo = commitInfo,
|
||||||
|
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
|
||||||
|
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
|
||||||
|
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
|
||||||
|
commentCount = getCommitComment(repository.owner, repository.name, sha).size
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
private def getAccount(userName: String, email: String): Account = {
|
||||||
|
getAccountByMailAddress(email).getOrElse {
|
||||||
|
Account(
|
||||||
|
userName = userName,
|
||||||
|
fullName = userName,
|
||||||
|
mailAddress = email,
|
||||||
|
password = "xxx",
|
||||||
|
isAdmin = false,
|
||||||
|
url = None,
|
||||||
|
registeredDate = new java.util.Date(),
|
||||||
|
updatedDate = new java.util.Date(),
|
||||||
|
lastLoginDate = None,
|
||||||
|
image = None,
|
||||||
|
isGroupAccount = false,
|
||||||
|
isRemoved = true,
|
||||||
|
description = None
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Get the SHA-1 of a commit reference
|
||||||
|
* https://developer.github.com/v3/repos/commits/#get-the-sha-1-of-a-commit-reference
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Compare two commits
|
||||||
|
* https://developer.github.com/v3/repos/commits/#compare-two-commits
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Commit signature verification
|
||||||
|
* https://developer.github.com/v3/repos/commits/#commit-signature-verification
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{ApiContents, JsonFormat}
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.service.{AccountService, ProtectedBranchService, RepositoryService}
|
||||||
|
import gitbucket.core.util.Directory.getRepositoryDir
|
||||||
|
import gitbucket.core.util.JGitUtil.{FileInfo, getContentFromId, getFileList}
|
||||||
|
import gitbucket.core.util._
|
||||||
|
import gitbucket.core.util.SyntaxSugars.using
|
||||||
|
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
|
||||||
|
trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||||
|
self: ReferrerAuthenticator =>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i. Get the README
|
||||||
|
* https://developer.github.com/v3/repos/contents/#get-the-readme
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ii. Get contents
|
||||||
|
* https://developer.github.com/v3/repos/contents/#get-contents
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/contents")(referrersOnly { repository =>
|
||||||
|
getContents(repository, ".", params.getOrElse("ref", repository.repository.defaultBranch))
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ii. Get contents
|
||||||
|
* https://developer.github.com/v3/repos/contents/#get-contents
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/contents/*")(referrersOnly { repository =>
|
||||||
|
getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch))
|
||||||
|
})
|
||||||
|
|
||||||
|
private def getContents(repository: RepositoryService.RepositoryInfo, path: String, refStr: String) = {
|
||||||
|
def getFileInfo(git: Git, revision: String, pathStr: String): Option[FileInfo] = {
|
||||||
|
val (dirName, fileName) = pathStr.lastIndexOf('/') match {
|
||||||
|
case -1 =>
|
||||||
|
(".", pathStr)
|
||||||
|
case n =>
|
||||||
|
(pathStr.take(n), pathStr.drop(n + 1))
|
||||||
|
}
|
||||||
|
getFileList(git, revision, dirName).find(f => f.name.equals(fileName))
|
||||||
|
}
|
||||||
|
|
||||||
|
using(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||||
|
val fileList = getFileList(git, refStr, path)
|
||||||
|
if (fileList.isEmpty) { // file or NotFound
|
||||||
|
getFileInfo(git, refStr, path)
|
||||||
|
.flatMap { f =>
|
||||||
|
val largeFile = params.get("large_file").exists(s => s.equals("true"))
|
||||||
|
val content = getContentFromId(git, f.id, largeFile)
|
||||||
|
request.getHeader("Accept") match {
|
||||||
|
case "application/vnd.github.v3.raw" => {
|
||||||
|
contentType = "application/vnd.github.v3.raw"
|
||||||
|
content
|
||||||
|
}
|
||||||
|
case "application/vnd.github.v3.html" if isRenderable(f.name) => {
|
||||||
|
contentType = "application/vnd.github.v3.html"
|
||||||
|
content.map { c =>
|
||||||
|
List(
|
||||||
|
"<div data-path=\"",
|
||||||
|
path,
|
||||||
|
"\" id=\"file\">",
|
||||||
|
"<article>",
|
||||||
|
renderMarkup(path.split("/").toList, new String(c), refStr, repository, false, false, true).body,
|
||||||
|
"</article>",
|
||||||
|
"</div>"
|
||||||
|
).mkString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "application/vnd.github.v3.html" => {
|
||||||
|
contentType = "application/vnd.github.v3.html"
|
||||||
|
content.map { c =>
|
||||||
|
List(
|
||||||
|
"<div data-path=\"",
|
||||||
|
path,
|
||||||
|
"\" id=\"file\">",
|
||||||
|
"<div class=\"plain\">",
|
||||||
|
"<pre>",
|
||||||
|
play.twirl.api.HtmlFormat.escape(new String(c)).body,
|
||||||
|
"</pre>",
|
||||||
|
"</div>",
|
||||||
|
"</div>"
|
||||||
|
).mkString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
Some(JsonFormat(ApiContents(f, RepositoryName(repository), content)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.getOrElse(NotFound())
|
||||||
|
|
||||||
|
} else { // directory
|
||||||
|
JsonFormat(fileList.map { f =>
|
||||||
|
ApiContents(f, RepositoryName(repository), None)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* iii. Create a file
|
||||||
|
* https://developer.github.com/v3/repos/contents/#create-a-file
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Update a file
|
||||||
|
* https://developer.github.com/v3/repos/contents/#update-a-file
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Delete a file
|
||||||
|
* https://developer.github.com/v3/repos/contents/#delete-a-file
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vi. Get archive link
|
||||||
|
* https://developer.github.com/v3/repos/contents/#get-archive-link
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api._
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.service.{AccountService, RepositoryCreationService, RepositoryService}
|
||||||
|
import gitbucket.core.servlet.Database
|
||||||
|
import gitbucket.core.util.Directory.getRepositoryDir
|
||||||
|
import gitbucket.core.util._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.SyntaxSugars.using
|
||||||
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
|
||||||
|
import scala.concurrent.Await
|
||||||
|
import scala.concurrent.duration.Duration
|
||||||
|
|
||||||
|
trait ApiRepositoryControllerBase extends ControllerBase {
|
||||||
|
self: RepositoryService
|
||||||
|
with RepositoryCreationService
|
||||||
|
with AccountService
|
||||||
|
with OwnerAuthenticator
|
||||||
|
with UsersAuthenticator
|
||||||
|
with GroupManagerAuthenticator
|
||||||
|
with ReferrerAuthenticator
|
||||||
|
with ReadableUsersAuthenticator
|
||||||
|
with WritableUsersAuthenticator =>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* i. List your repositories
|
||||||
|
* https://developer.github.com/v3/repos/#list-your-repositories
|
||||||
|
*/
|
||||||
|
get("/api/v3/user/repos")(usersOnly {
|
||||||
|
JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map { r =>
|
||||||
|
ApiRepository(r, getAccountByUserName(r.owner).get)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ii. List user repositories
|
||||||
|
* https://developer.github.com/v3/repos/#list-user-repositories
|
||||||
|
*/
|
||||||
|
get("/api/v3/users/:userName/repos") {
|
||||||
|
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map { r =>
|
||||||
|
ApiRepository(r, getAccountByUserName(r.owner).get)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iii. List organization repositories
|
||||||
|
* https://developer.github.com/v3/repos/#list-organization-repositories
|
||||||
|
*/
|
||||||
|
get("/api/v3/orgs/:orgName/repos") {
|
||||||
|
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map { r =>
|
||||||
|
ApiRepository(r, getAccountByUserName(r.owner).get)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. List all public repositories
|
||||||
|
* https://developer.github.com/v3/repos/#list-all-public-repositories
|
||||||
|
* Not implemented
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Create
|
||||||
|
* https://developer.github.com/v3/repos/#create
|
||||||
|
* Implemented with two methods (user/orgs)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
val f = createRepository(
|
||||||
|
context.loginAccount.get,
|
||||||
|
owner,
|
||||||
|
data.name,
|
||||||
|
data.description,
|
||||||
|
data.`private`,
|
||||||
|
data.auto_init
|
||||||
|
)
|
||||||
|
Await.result(f, Duration.Inf)
|
||||||
|
|
||||||
|
val repository = Database() withTransaction { session =>
|
||||||
|
getRepository(owner, data.name)(session).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) {
|
||||||
|
val f = createRepository(
|
||||||
|
context.loginAccount.get,
|
||||||
|
groupName,
|
||||||
|
data.name,
|
||||||
|
data.description,
|
||||||
|
data.`private`,
|
||||||
|
data.auto_init
|
||||||
|
)
|
||||||
|
Await.result(f, Duration.Inf)
|
||||||
|
val repository = Database() withTransaction { session =>
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vi. Get
|
||||||
|
* https://developer.github.com/v3/repos/#get
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
|
||||||
|
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vii. Edit
|
||||||
|
* https://developer.github.com/v3/repos/#edit
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* viii. List all topics for a repository
|
||||||
|
* https://developer.github.com/v3/repos/#list-all-topics-for-a-repository
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ix. Replace all topics for a repository
|
||||||
|
* https://developer.github.com/v3/repos/#replace-all-topics-for-a-repository
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* x. List contributors
|
||||||
|
* https://developer.github.com/v3/repos/#list-contributors
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xi. List languages
|
||||||
|
* https://developer.github.com/v3/repos/#list-languages
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xii. List teams
|
||||||
|
* https://developer.github.com/v3/repos/#list-teams
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xiii. List tags
|
||||||
|
* https://developer.github.com/v3/repos/#list-tags
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xiv. Delete a repository
|
||||||
|
* https://developer.github.com/v3/repos/#delete-a-repository
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* xv. Transfer a repository
|
||||||
|
* https://developer.github.com/v3/repos/#transfer-a-repository
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* non-GitHub compatible API for Jenkins-Plugin
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/raw/*")(referrersOnly { repository =>
|
||||||
|
val (id, path) = repository.splitPath(multiParams("splat").head)
|
||||||
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
|
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||||
|
|
||||||
|
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||||
|
responseRawFile(git, objectId, path, repository)
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api._
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.model.CommitState
|
||||||
|
import gitbucket.core.service.{AccountService, CommitStatusService}
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.{JGitUtil, ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||||
|
|
||||||
|
trait ApiRepositoryStatusControllerBase extends ControllerBase {
|
||||||
|
self: AccountService with CommitStatusService with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* i. Create a status
|
||||||
|
* https://developer.github.com/v3/repos/statuses/#create-a-status
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/statuses/:sha")(writableUsersOnly { 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()
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ii. List statuses for a specific ref
|
||||||
|
* 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/:repository/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/:repository/statuses/:ref") {
|
||||||
|
listStatusesRoute.action()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Get the combined status for a specific ref
|
||||||
|
* 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/:repository/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()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package gitbucket.core.controller.api
|
||||||
|
import gitbucket.core.api.{ApiUser, JsonFormat}
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
|
||||||
|
trait ApiUserControllerBase extends ControllerBase {
|
||||||
|
self: RepositoryService with AccountService =>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* i. Get a single user
|
||||||
|
* https://developer.github.com/v3/users/#get-a-single-user
|
||||||
|
* This API also returns group information (as GitHub).
|
||||||
|
*/
|
||||||
|
get("/api/v3/users/:userName") {
|
||||||
|
getAccountByUserName(params("userName")).map { account =>
|
||||||
|
JsonFormat(ApiUser(account))
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ii. Get the authenticated user
|
||||||
|
* https://developer.github.com/v3/users/#get-the-authenticated-user
|
||||||
|
*/
|
||||||
|
get("/api/v3/user") {
|
||||||
|
context.loginAccount.map { account =>
|
||||||
|
JsonFormat(ApiUser(account))
|
||||||
|
} getOrElse Unauthorized()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iii. Update the authenticated user
|
||||||
|
* https://developer.github.com/v3/users/#update-the-authenticated-user
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iv. Get contextual information about a user
|
||||||
|
* https://developer.github.com/v3/users/#get-contextual-information-about-a-user
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* v. Get all users
|
||||||
|
* https://developer.github.com/v3/users/#get-all-users
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user