Merge pull request #2163 from kounoike/pr-refactor-apicontroller

Split ApiController
This commit is contained in:
Naoki Takezoe
2018-10-07 18:58:29 +09:00
committed by GitHub
14 changed files with 1422 additions and 800 deletions

View File

@@ -1,29 +1,17 @@
package gitbucket.core.controller
import gitbucket.core.api._
import gitbucket.core.model._
import gitbucket.core.service.IssuesService.IssueSearchCondition
import gitbucket.core.service.PullRequestService._
import gitbucket.core.controller.api.{ApiOrganizationControllerBase, ApiRepositoryControllerBase, ApiUserControllerBase}
import gitbucket.core.service._
import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.JGitUtil._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util._
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
extends ApiControllerBase
with ApiOrganizationControllerBase
with ApiRepositoryControllerBase
with ApiUserControllerBase
with RepositoryService
with AccountService
with ProtectedBranchService
@@ -50,25 +38,6 @@ class ApiController
with WritableUsersAuthenticator
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
@@ -76,6 +45,18 @@ trait ApiControllerBase extends ControllerBase {
get("/api/v3/*") {
NotFound()
}
post("/api/v3/*") {
NotFound()
}
put("/api/v3/*") {
NotFound()
}
delete("/api/v3/*") {
NotFound()
}
patch("/api/v3/*") {
NotFound()
}
/**
* https://developer.github.com/v3/#root-endpoint
@@ -84,318 +65,6 @@ trait ApiControllerBase extends ControllerBase {
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
* but not enabled.
@@ -406,459 +75,6 @@ trait ApiControllerBase extends ControllerBase {
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
*/

View File

@@ -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
*/
}

View File

@@ -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
}

View File

@@ -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
*/
}

View File

@@ -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
*/
}

View File

@@ -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()
}
}

View File

@@ -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
*/
}

View File

@@ -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()
})
}

View File

@@ -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
*/
}

View File

@@ -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
*/
}

View File

@@ -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
*/
}

View File

@@ -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()
}
})
}

View File

@@ -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()
})
}

View File

@@ -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
*/
}