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