diff --git a/src/main/scala/gitbucket/core/controller/ApiController.scala b/src/main/scala/gitbucket/core/controller/ApiController.scala
index 685a8b4..049f59b 100644
--- a/src/main/scala/gitbucket/core/controller/ApiController.scala
+++ b/src/main/scala/gitbucket/core/controller/ApiController.scala
@@ -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 @@
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 @@
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
@@ -85,318 +66,6 @@
}
/**
- * 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(
- "
",
- "
",
- renderMarkup(path.split("/").toList, new String(c), refStr, repository, false, false, true).body,
- "",
- "
"
- ).mkString
- }
- }
- case "application/vnd.github.v3.html" => {
- contentType = "application/vnd.github.v3.html"
- content.map { c =>
- List(
- "",
- "
",
- "
",
- play.twirl.api.HtmlFormat.escape(new String(c)).body,
- "
",
- "
",
- "
"
- ).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.
*/
@@ -407,459 +76,6 @@
}
/**
- * 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
*/
get("/api/v3/gitbucket/plugins") {
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiGitReferenceControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiGitReferenceControllerBase.scala
new file mode 100644
index 0000000..9069733
--- /dev/null
+++ b/src/main/scala/gitbucket/core/controller/api/ApiGitReferenceControllerBase.scala
@@ -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
+ */
+}
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiIssueCommentControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiIssueCommentControllerBase.scala
new file mode 100644
index 0000000..89d8033
--- /dev/null
+++ b/src/main/scala/gitbucket/core/controller/api/ApiIssueCommentControllerBase.scala
@@ -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
+}
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiIssueControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiIssueControllerBase.scala
new file mode 100644
index 0000000..6fe0632
--- /dev/null
+++ b/src/main/scala/gitbucket/core/controller/api/ApiIssueControllerBase.scala
@@ -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
+ */
+}
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiIssueLabelControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiIssueLabelControllerBase.scala
new file mode 100644
index 0000000..0f9e4cc
--- /dev/null
+++ b/src/main/scala/gitbucket/core/controller/api/ApiIssueLabelControllerBase.scala
@@ -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
+ */
+}
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiOrganizationControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiOrganizationControllerBase.scala
new file mode 100644
index 0000000..0b64c9f
--- /dev/null
+++ b/src/main/scala/gitbucket/core/controller/api/ApiOrganizationControllerBase.scala
@@ -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()
+ }
+}
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala
new file mode 100644
index 0000000..fe66183
--- /dev/null
+++ b/src/main/scala/gitbucket/core/controller/api/ApiPullRequestControllerBase.scala
@@ -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
+ */
+}
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryBranchControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryBranchControllerBase.scala
new file mode 100644
index 0000000..fa2d59a
--- /dev/null
+++ b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryBranchControllerBase.scala
@@ -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()
+ })
+}
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryCollaboratorControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryCollaboratorControllerBase.scala
new file mode 100644
index 0000000..402b7a3
--- /dev/null
+++ b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryCollaboratorControllerBase.scala
@@ -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
+ */
+}
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryCommitControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryCommitControllerBase.scala
new file mode 100644
index 0000000..5455f9c
--- /dev/null
+++ b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryCommitControllerBase.scala
@@ -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
+ */
+}
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryContentsControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryContentsControllerBase.scala
new file mode 100644
index 0000000..c1d128b
--- /dev/null
+++ b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryContentsControllerBase.scala
@@ -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(
+ "",
+ "
",
+ renderMarkup(path.split("/").toList, new String(c), refStr, repository, false, false, true).body,
+ "",
+ "
"
+ ).mkString
+ }
+ }
+ case "application/vnd.github.v3.html" => {
+ contentType = "application/vnd.github.v3.html"
+ content.map { c =>
+ List(
+ "",
+ "
",
+ "
",
+ play.twirl.api.HtmlFormat.escape(new String(c)).body,
+ "
",
+ "
",
+ "
"
+ ).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
+ */
+
+}
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryControllerBase.scala
new file mode 100644
index 0000000..5c4b13d
--- /dev/null
+++ b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryControllerBase.scala
@@ -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()
+ }
+ })
+}
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryStatusControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryStatusControllerBase.scala
new file mode 100644
index 0000000..787acda
--- /dev/null
+++ b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryStatusControllerBase.scala
@@ -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()
+ })
+}
diff --git a/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala
new file mode 100644
index 0000000..7c596fd
--- /dev/null
+++ b/src/main/scala/gitbucket/core/controller/api/ApiUserControllerBase.scala
@@ -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
+ */
+
+}