aboutsummaryrefslogtreecommitdiff
path: root/internal/route/api/v1/repo
diff options
context:
space:
mode:
authorUnknwon <u@gogs.io>2019-10-24 01:51:46 -0700
committerGitHub <noreply@github.com>2019-10-24 01:51:46 -0700
commit01c8df01ec0608f1f25b2f1444adabb98fa5ee8a (patch)
treef8a7e5dd8d2a8c51e1ce2cabb9d33571a93314dd /internal/route/api/v1/repo
parent613139e7bef81d3573e7988a47eb6765f3de347a (diff)
internal: move packages under this directory (#5836)
* Rename pkg -> internal * Rename routes -> route * Move route -> internal/route * Rename models -> db * Move db -> internal/db * Fix route2 -> route * Move cmd -> internal/cmd * Bump version
Diffstat (limited to 'internal/route/api/v1/repo')
-rw-r--r--internal/route/api/v1/repo/branch.go55
-rw-r--r--internal/route/api/v1/repo/collaborators.go90
-rw-r--r--internal/route/api/v1/repo/commits.go138
-rw-r--r--internal/route/api/v1/repo/file.go62
-rw-r--r--internal/route/api/v1/repo/hook.go185
-rw-r--r--internal/route/api/v1/repo/issue.go194
-rw-r--r--internal/route/api/v1/repo/issue_comment.go131
-rw-r--r--internal/route/api/v1/repo/issue_label.go131
-rw-r--r--internal/route/api/v1/repo/key.go114
-rw-r--r--internal/route/api/v1/repo/label.go89
-rw-r--r--internal/route/api/v1/repo/milestone.go96
-rw-r--r--internal/route/api/v1/repo/repo.go407
12 files changed, 1692 insertions, 0 deletions
diff --git a/internal/route/api/v1/repo/branch.go b/internal/route/api/v1/repo/branch.go
new file mode 100644
index 00000000..b90d1e24
--- /dev/null
+++ b/internal/route/api/v1/repo/branch.go
@@ -0,0 +1,55 @@
+// Copyright 2016 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+ api "github.com/gogs/go-gogs-client"
+ convert2 "gogs.io/gogs/internal/route/api/v1/convert"
+
+ "gogs.io/gogs/internal/context"
+ "gogs.io/gogs/internal/db/errors"
+)
+
+// https://github.com/gogs/go-gogs-client/wiki/Repositories#get-branch
+func GetBranch(c *context.APIContext) {
+ branch, err := c.Repo.Repository.GetBranch(c.Params("*"))
+ if err != nil {
+ if errors.IsErrBranchNotExist(err) {
+ c.Error(404, "GetBranch", err)
+ } else {
+ c.Error(500, "GetBranch", err)
+ }
+ return
+ }
+
+ commit, err := branch.GetCommit()
+ if err != nil {
+ c.Error(500, "GetCommit", err)
+ return
+ }
+
+ c.JSON(200, convert2.ToBranch(branch, commit))
+}
+
+// https://github.com/gogs/go-gogs-client/wiki/Repositories#list-branches
+func ListBranches(c *context.APIContext) {
+ branches, err := c.Repo.Repository.GetBranches()
+ if err != nil {
+ c.Error(500, "GetBranches", err)
+ return
+ }
+
+ apiBranches := make([]*api.Branch, len(branches))
+ for i := range branches {
+ commit, err := branches[i].GetCommit()
+ if err != nil {
+ c.Error(500, "GetCommit", err)
+ return
+ }
+ apiBranches[i] = convert2.ToBranch(branches[i], commit)
+ }
+
+ c.JSON(200, &apiBranches)
+}
diff --git a/internal/route/api/v1/repo/collaborators.go b/internal/route/api/v1/repo/collaborators.go
new file mode 100644
index 00000000..e8f74848
--- /dev/null
+++ b/internal/route/api/v1/repo/collaborators.go
@@ -0,0 +1,90 @@
+// Copyright 2016 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+ api "github.com/gogs/go-gogs-client"
+
+ "gogs.io/gogs/internal/context"
+ "gogs.io/gogs/internal/db"
+ "gogs.io/gogs/internal/db/errors"
+)
+
+func ListCollaborators(c *context.APIContext) {
+ collaborators, err := c.Repo.Repository.GetCollaborators()
+ if err != nil {
+ c.ServerError("GetCollaborators", err)
+ return
+ }
+
+ apiCollaborators := make([]*api.Collaborator, len(collaborators))
+ for i := range collaborators {
+ apiCollaborators[i] = collaborators[i].APIFormat()
+ }
+ c.JSONSuccess(&apiCollaborators)
+}
+
+func AddCollaborator(c *context.APIContext, form api.AddCollaboratorOption) {
+ collaborator, err := db.GetUserByName(c.Params(":collaborator"))
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "GetUserByName", err)
+ }
+ return
+ }
+
+ if err := c.Repo.Repository.AddCollaborator(collaborator); err != nil {
+ c.Error(500, "AddCollaborator", err)
+ return
+ }
+
+ if form.Permission != nil {
+ if err := c.Repo.Repository.ChangeCollaborationAccessMode(collaborator.ID, db.ParseAccessMode(*form.Permission)); err != nil {
+ c.Error(500, "ChangeCollaborationAccessMode", err)
+ return
+ }
+ }
+
+ c.Status(204)
+}
+
+func IsCollaborator(c *context.APIContext) {
+ collaborator, err := db.GetUserByName(c.Params(":collaborator"))
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "GetUserByName", err)
+ }
+ return
+ }
+
+ if !c.Repo.Repository.IsCollaborator(collaborator.ID) {
+ c.Status(404)
+ } else {
+ c.Status(204)
+ }
+}
+
+func DeleteCollaborator(c *context.APIContext) {
+ collaborator, err := db.GetUserByName(c.Params(":collaborator"))
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(422, "", err)
+ } else {
+ c.Error(500, "GetUserByName", err)
+ }
+ return
+ }
+
+ if err := c.Repo.Repository.DeleteCollaboration(collaborator.ID); err != nil {
+ c.Error(500, "DeleteCollaboration", err)
+ return
+ }
+
+ c.Status(204)
+}
diff --git a/internal/route/api/v1/repo/commits.go b/internal/route/api/v1/repo/commits.go
new file mode 100644
index 00000000..55bfc045
--- /dev/null
+++ b/internal/route/api/v1/repo/commits.go
@@ -0,0 +1,138 @@
+// Copyright 2018 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/gogs/git-module"
+ api "github.com/gogs/go-gogs-client"
+
+ "gogs.io/gogs/internal/context"
+ "gogs.io/gogs/internal/db"
+ "gogs.io/gogs/internal/db/errors"
+ "gogs.io/gogs/internal/setting"
+)
+
+func GetSingleCommit(c *context.APIContext) {
+ if strings.Contains(c.Req.Header.Get("Accept"), api.MediaApplicationSHA) {
+ c.SetParams("*", c.Params(":sha"))
+ GetReferenceSHA(c)
+ return
+ }
+
+ gitRepo, err := git.OpenRepository(c.Repo.Repository.RepoPath())
+ if err != nil {
+ c.ServerError("OpenRepository", err)
+ return
+ }
+ commit, err := gitRepo.GetCommit(c.Params(":sha"))
+ if err != nil {
+ c.NotFoundOrServerError("GetCommit", git.IsErrNotExist, err)
+ return
+ }
+
+ // Retrieve author and committer information
+ var apiAuthor, apiCommitter *api.User
+ author, err := db.GetUserByEmail(commit.Author.Email)
+ if err != nil && !errors.IsUserNotExist(err) {
+ c.ServerError("Get user by author email", err)
+ return
+ } else if err == nil {
+ apiAuthor = author.APIFormat()
+ }
+ // Save one query if the author is also the committer
+ if commit.Committer.Email == commit.Author.Email {
+ apiCommitter = apiAuthor
+ } else {
+ committer, err := db.GetUserByEmail(commit.Committer.Email)
+ if err != nil && !errors.IsUserNotExist(err) {
+ c.ServerError("Get user by committer email", err)
+ return
+ } else if err == nil {
+ apiCommitter = committer.APIFormat()
+ }
+ }
+
+ // Retrieve parent(s) of the commit
+ apiParents := make([]*api.CommitMeta, commit.ParentCount())
+ for i := 0; i < commit.ParentCount(); i++ {
+ sha, _ := commit.ParentID(i)
+ apiParents[i] = &api.CommitMeta{
+ URL: c.BaseURL + "/repos/" + c.Repo.Repository.FullName() + "/commits/" + sha.String(),
+ SHA: sha.String(),
+ }
+ }
+
+ c.JSONSuccess(&api.Commit{
+ CommitMeta: &api.CommitMeta{
+ URL: setting.AppURL + c.Link[1:],
+ SHA: commit.ID.String(),
+ },
+ HTMLURL: c.Repo.Repository.HTMLURL() + "/commits/" + commit.ID.String(),
+ RepoCommit: &api.RepoCommit{
+ URL: setting.AppURL + c.Link[1:],
+ Author: &api.CommitUser{
+ Name: commit.Author.Name,
+ Email: commit.Author.Email,
+ Date: commit.Author.When.Format(time.RFC3339),
+ },
+ Committer: &api.CommitUser{
+ Name: commit.Committer.Name,
+ Email: commit.Committer.Email,
+ Date: commit.Committer.When.Format(time.RFC3339),
+ },
+ Message: commit.Summary(),
+ Tree: &api.CommitMeta{
+ URL: c.BaseURL + "/repos/" + c.Repo.Repository.FullName() + "/tree/" + commit.ID.String(),
+ SHA: commit.ID.String(),
+ },
+ },
+ Author: apiAuthor,
+ Committer: apiCommitter,
+ Parents: apiParents,
+ })
+}
+
+func GetReferenceSHA(c *context.APIContext) {
+ gitRepo, err := git.OpenRepository(c.Repo.Repository.RepoPath())
+ if err != nil {
+ c.ServerError("OpenRepository", err)
+ return
+ }
+
+ ref := c.Params("*")
+ refType := 0 // 0-undetermined, 1-branch, 2-tag
+ if strings.HasPrefix(ref, git.BRANCH_PREFIX) {
+ ref = strings.TrimPrefix(ref, git.BRANCH_PREFIX)
+ refType = 1
+ } else if strings.HasPrefix(ref, git.TAG_PREFIX) {
+ ref = strings.TrimPrefix(ref, git.TAG_PREFIX)
+ refType = 2
+ } else {
+ if gitRepo.IsBranchExist(ref) {
+ refType = 1
+ } else if gitRepo.IsTagExist(ref) {
+ refType = 2
+ } else {
+ c.NotFound()
+ return
+ }
+ }
+
+ var sha string
+ if refType == 1 {
+ sha, err = gitRepo.GetBranchCommitID(ref)
+ } else if refType == 2 {
+ sha, err = gitRepo.GetTagCommitID(ref)
+ }
+ if err != nil {
+ c.NotFoundOrServerError("get reference commit ID", git.IsErrNotExist, err)
+ return
+ }
+ c.PlainText(http.StatusOK, []byte(sha))
+}
diff --git a/internal/route/api/v1/repo/file.go b/internal/route/api/v1/repo/file.go
new file mode 100644
index 00000000..4dcae313
--- /dev/null
+++ b/internal/route/api/v1/repo/file.go
@@ -0,0 +1,62 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+ "github.com/gogs/git-module"
+ repo2 "gogs.io/gogs/internal/route/repo"
+
+ "gogs.io/gogs/internal/context"
+ "gogs.io/gogs/internal/db"
+)
+
+func GetRawFile(c *context.APIContext) {
+ if !c.Repo.HasAccess() {
+ c.NotFound()
+ return
+ }
+
+ if c.Repo.Repository.IsBare {
+ c.NotFound()
+ return
+ }
+
+ blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath)
+ if err != nil {
+ c.NotFoundOrServerError("GetBlobByPath", git.IsErrNotExist, err)
+ return
+ }
+ if err = repo2.ServeBlob(c.Context, blob); err != nil {
+ c.ServerError("ServeBlob", err)
+ }
+}
+
+func GetArchive(c *context.APIContext) {
+ repoPath := db.RepoPath(c.Params(":username"), c.Params(":reponame"))
+ gitRepo, err := git.OpenRepository(repoPath)
+ if err != nil {
+ c.ServerError("OpenRepository", err)
+ return
+ }
+ c.Repo.GitRepo = gitRepo
+
+ repo2.Download(c.Context)
+}
+
+func GetEditorconfig(c *context.APIContext) {
+ ec, err := c.Repo.GetEditorconfig()
+ if err != nil {
+ c.NotFoundOrServerError("GetEditorconfig", git.IsErrNotExist, err)
+ return
+ }
+
+ fileName := c.Params("filename")
+ def := ec.GetDefinitionForFilename(fileName)
+ if def == nil {
+ c.NotFound()
+ return
+ }
+ c.JSONSuccess(def)
+}
diff --git a/internal/route/api/v1/repo/hook.go b/internal/route/api/v1/repo/hook.go
new file mode 100644
index 00000000..060d2049
--- /dev/null
+++ b/internal/route/api/v1/repo/hook.go
@@ -0,0 +1,185 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+ "github.com/json-iterator/go"
+ "github.com/unknwon/com"
+ convert2 "gogs.io/gogs/internal/route/api/v1/convert"
+
+ api "github.com/gogs/go-gogs-client"
+
+ "gogs.io/gogs/internal/context"
+ "gogs.io/gogs/internal/db"
+ "gogs.io/gogs/internal/db/errors"
+)
+
+// https://github.com/gogs/go-gogs-client/wiki/Repositories#list-hooks
+func ListHooks(c *context.APIContext) {
+ hooks, err := db.GetWebhooksByRepoID(c.Repo.Repository.ID)
+ if err != nil {
+ c.Error(500, "GetWebhooksByRepoID", err)
+ return
+ }
+
+ apiHooks := make([]*api.Hook, len(hooks))
+ for i := range hooks {
+ apiHooks[i] = convert2.ToHook(c.Repo.RepoLink, hooks[i])
+ }
+ c.JSON(200, &apiHooks)
+}
+
+// https://github.com/gogs/go-gogs-client/wiki/Repositories#create-a-hook
+func CreateHook(c *context.APIContext, form api.CreateHookOption) {
+ if !db.IsValidHookTaskType(form.Type) {
+ c.Error(422, "", "Invalid hook type")
+ return
+ }
+ for _, name := range []string{"url", "content_type"} {
+ if _, ok := form.Config[name]; !ok {
+ c.Error(422, "", "Missing config option: "+name)
+ return
+ }
+ }
+ if !db.IsValidHookContentType(form.Config["content_type"]) {
+ c.Error(422, "", "Invalid content type")
+ return
+ }
+
+ if len(form.Events) == 0 {
+ form.Events = []string{"push"}
+ }
+ w := &db.Webhook{
+ RepoID: c.Repo.Repository.ID,
+ URL: form.Config["url"],
+ ContentType: db.ToHookContentType(form.Config["content_type"]),
+ Secret: form.Config["secret"],
+ HookEvent: &db.HookEvent{
+ ChooseEvents: true,
+ HookEvents: db.HookEvents{
+ Create: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_CREATE)),
+ Delete: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_DELETE)),
+ Fork: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_FORK)),
+ Push: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_PUSH)),
+ Issues: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_ISSUES)),
+ IssueComment: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_ISSUE_COMMENT)),
+ PullRequest: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_PULL_REQUEST)),
+ Release: com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_RELEASE)),
+ },
+ },
+ IsActive: form.Active,
+ HookTaskType: db.ToHookTaskType(form.Type),
+ }
+ if w.HookTaskType == db.SLACK {
+ channel, ok := form.Config["channel"]
+ if !ok {
+ c.Error(422, "", "Missing config option: channel")
+ return
+ }
+ meta, err := jsoniter.Marshal(&db.SlackMeta{
+ Channel: channel,
+ Username: form.Config["username"],
+ IconURL: form.Config["icon_url"],
+ Color: form.Config["color"],
+ })
+ if err != nil {
+ c.Error(500, "slack: JSON marshal failed", err)
+ return
+ }
+ w.Meta = string(meta)
+ }
+
+ if err := w.UpdateEvent(); err != nil {
+ c.Error(500, "UpdateEvent", err)
+ return
+ } else if err := db.CreateWebhook(w); err != nil {
+ c.Error(500, "CreateWebhook", err)
+ return
+ }
+
+ c.JSON(201, convert2.ToHook(c.Repo.RepoLink, w))
+}
+
+// https://github.com/gogs/go-gogs-client/wiki/Repositories#edit-a-hook
+func EditHook(c *context.APIContext, form api.EditHookOption) {
+ w, err := db.GetWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
+ if err != nil {
+ if errors.IsWebhookNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetWebhookOfRepoByID", err)
+ }
+ return
+ }
+
+ if form.Config != nil {
+ if url, ok := form.Config["url"]; ok {
+ w.URL = url
+ }
+ if ct, ok := form.Config["content_type"]; ok {
+ if !db.IsValidHookContentType(ct) {
+ c.Error(422, "", "Invalid content type")
+ return
+ }
+ w.ContentType = db.ToHookContentType(ct)
+ }
+
+ if w.HookTaskType == db.SLACK {
+ if channel, ok := form.Config["channel"]; ok {
+ meta, err := jsoniter.Marshal(&db.SlackMeta{
+ Channel: channel,
+ Username: form.Config["username"],
+ IconURL: form.Config["icon_url"],
+ Color: form.Config["color"],
+ })
+ if err != nil {
+ c.Error(500, "slack: JSON marshal failed", err)
+ return
+ }
+ w.Meta = string(meta)
+ }
+ }
+ }
+
+ // Update events
+ if len(form.Events) == 0 {
+ form.Events = []string{"push"}
+ }
+ w.PushOnly = false
+ w.SendEverything = false
+ w.ChooseEvents = true
+ w.Create = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_CREATE))
+ w.Delete = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_DELETE))
+ w.Fork = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_FORK))
+ w.Push = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_PUSH))
+ w.Issues = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_ISSUES))
+ w.IssueComment = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_ISSUE_COMMENT))
+ w.PullRequest = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_PULL_REQUEST))
+ w.Release = com.IsSliceContainsStr(form.Events, string(db.HOOK_EVENT_RELEASE))
+ if err = w.UpdateEvent(); err != nil {
+ c.Error(500, "UpdateEvent", err)
+ return
+ }
+
+ if form.Active != nil {
+ w.IsActive = *form.Active
+ }
+
+ if err := db.UpdateWebhook(w); err != nil {
+ c.Error(500, "UpdateWebhook", err)
+ return
+ }
+
+ c.JSON(200, convert2.ToHook(c.Repo.RepoLink, w))
+}
+
+func DeleteHook(c *context.APIContext) {
+ if err := db.DeleteWebhookOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil {
+ c.Error(500, "DeleteWebhookByRepoID", err)
+ return
+ }
+
+ c.Status(204)
+}
diff --git a/internal/route/api/v1/repo/issue.go b/internal/route/api/v1/repo/issue.go
new file mode 100644
index 00000000..5d32a00c
--- /dev/null
+++ b/internal/route/api/v1/repo/issue.go
@@ -0,0 +1,194 @@
+// Copyright 2016 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ api "github.com/gogs/go-gogs-client"
+
+ "gogs.io/gogs/internal/context"
+ "gogs.io/gogs/internal/db"
+ "gogs.io/gogs/internal/db/errors"
+ "gogs.io/gogs/internal/setting"
+)
+
+func listIssues(c *context.APIContext, opts *db.IssuesOptions) {
+ issues, err := db.Issues(opts)
+ if err != nil {
+ c.ServerError("Issues", err)
+ return
+ }
+
+ count, err := db.IssuesCount(opts)
+ if err != nil {
+ c.ServerError("IssuesCount", err)
+ return
+ }
+
+ // FIXME: use IssueList to improve performance.
+ apiIssues := make([]*api.Issue, len(issues))
+ for i := range issues {
+ if err = issues[i].LoadAttributes(); err != nil {
+ c.ServerError("LoadAttributes", err)
+ return
+ }
+ apiIssues[i] = issues[i].APIFormat()
+ }
+
+ c.SetLinkHeader(int(count), setting.UI.IssuePagingNum)
+ c.JSONSuccess(&apiIssues)
+}
+
+func ListUserIssues(c *context.APIContext) {
+ opts := db.IssuesOptions{
+ AssigneeID: c.User.ID,
+ Page: c.QueryInt("page"),
+ IsClosed: api.StateType(c.Query("state")) == api.STATE_CLOSED,
+ }
+
+ listIssues(c, &opts)
+}
+
+func ListIssues(c *context.APIContext) {
+ opts := db.IssuesOptions{
+ RepoID: c.Repo.Repository.ID,
+ Page: c.QueryInt("page"),
+ IsClosed: api.StateType(c.Query("state")) == api.STATE_CLOSED,
+ }
+
+ listIssues(c, &opts)
+}
+
+func GetIssue(c *context.APIContext) {
+ issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
+ return
+ }
+ c.JSONSuccess(issue.APIFormat())
+}
+
+func CreateIssue(c *context.APIContext, form api.CreateIssueOption) {
+ issue := &db.Issue{
+ RepoID: c.Repo.Repository.ID,
+ Title: form.Title,
+ PosterID: c.User.ID,
+ Poster: c.User,
+ Content: form.Body,
+ }
+
+ if c.Repo.IsWriter() {
+ if len(form.Assignee) > 0 {
+ assignee, err := db.GetUserByName(form.Assignee)
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("assignee does not exist: [name: %s]", form.Assignee))
+ } else {
+ c.ServerError("GetUserByName", err)
+ }
+ return
+ }
+ issue.AssigneeID = assignee.ID
+ }
+ issue.MilestoneID = form.Milestone
+ } else {
+ form.Labels = nil
+ }
+
+ if err := db.NewIssue(c.Repo.Repository, issue, form.Labels, nil); err != nil {
+ c.ServerError("NewIssue", err)
+ return
+ }
+
+ if form.Closed {
+ if err := issue.ChangeStatus(c.User, c.Repo.Repository, true); err != nil {
+ c.ServerError("ChangeStatus", err)
+ return
+ }
+ }
+
+ // Refetch from database to assign some automatic values
+ var err error
+ issue, err = db.GetIssueByID(issue.ID)
+ if err != nil {
+ c.ServerError("GetIssueByID", err)
+ return
+ }
+ c.JSON(http.StatusCreated, issue.APIFormat())
+}
+
+func EditIssue(c *context.APIContext, form api.EditIssueOption) {
+ issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
+ return
+ }
+
+ if !issue.IsPoster(c.User.ID) && !c.Repo.IsWriter() {
+ c.Status(http.StatusForbidden)
+ return
+ }
+
+ if len(form.Title) > 0 {
+ issue.Title = form.Title
+ }
+ if form.Body != nil {
+ issue.Content = *form.Body
+ }
+
+ if c.Repo.IsWriter() && form.Assignee != nil &&
+ (issue.Assignee == nil || issue.Assignee.LowerName != strings.ToLower(*form.Assignee)) {
+ if len(*form.Assignee) == 0 {
+ issue.AssigneeID = 0
+ } else {
+ assignee, err := db.GetUserByName(*form.Assignee)
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("assignee does not exist: [name: %s]", *form.Assignee))
+ } else {
+ c.ServerError("GetUserByName", err)
+ }
+ return
+ }
+ issue.AssigneeID = assignee.ID
+ }
+
+ if err = db.UpdateIssueUserByAssignee(issue); err != nil {
+ c.ServerError("UpdateIssueUserByAssignee", err)
+ return
+ }
+ }
+ if c.Repo.IsWriter() && form.Milestone != nil &&
+ issue.MilestoneID != *form.Milestone {
+ oldMilestoneID := issue.MilestoneID
+ issue.MilestoneID = *form.Milestone
+ if err = db.ChangeMilestoneAssign(c.User, issue, oldMilestoneID); err != nil {
+ c.ServerError("ChangeMilestoneAssign", err)
+ return
+ }
+ }
+
+ if err = db.UpdateIssue(issue); err != nil {
+ c.ServerError("UpdateIssue", err)
+ return
+ }
+ if form.State != nil {
+ if err = issue.ChangeStatus(c.User, c.Repo.Repository, api.STATE_CLOSED == api.StateType(*form.State)); err != nil {
+ c.ServerError("ChangeStatus", err)
+ return
+ }
+ }
+
+ // Refetch from database to assign some automatic values
+ issue, err = db.GetIssueByID(issue.ID)
+ if err != nil {
+ c.ServerError("GetIssueByID", err)
+ return
+ }
+ c.JSON(http.StatusCreated, issue.APIFormat())
+}
diff --git a/internal/route/api/v1/repo/issue_comment.go b/internal/route/api/v1/repo/issue_comment.go
new file mode 100644
index 00000000..4f86e13b
--- /dev/null
+++ b/internal/route/api/v1/repo/issue_comment.go
@@ -0,0 +1,131 @@
+// Copyright 2015 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+package repo
+
+import (
+ "net/http"
+ "time"
+
+ api "github.com/gogs/go-gogs-client"
+
+ "gogs.io/gogs/internal/context"
+ "gogs.io/gogs/internal/db"
+)
+
+func ListIssueComments(c *context.APIContext) {
+ var since time.Time
+ if len(c.Query("since")) > 0 {
+ var err error
+ since, err = time.Parse(time.RFC3339, c.Query("since"))
+ if err != nil {
+ c.Error(http.StatusUnprocessableEntity, "", err)
+ return
+ }
+ }
+
+ // comments,err:=db.GetCommentsByIssueIDSince(, since)
+ issue, err := db.GetRawIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ c.ServerError("GetRawIssueByIndex", err)
+ return
+ }
+
+ comments, err := db.GetCommentsByIssueIDSince(issue.ID, since.Unix())
+ if err != nil {
+ c.ServerError("GetCommentsByIssueIDSince", err)
+ return
+ }
+
+ apiComments := make([]*api.Comment, len(comments))
+ for i := range comments {
+ apiComments[i] = comments[i].APIFormat()
+ }
+ c.JSONSuccess(&apiComments)
+}
+
+func ListRepoIssueComments(c *context.APIContext) {
+ var since time.Time
+ if len(c.Query("since")) > 0 {
+ var err error
+ since, err = time.Parse(time.RFC3339, c.Query("since"))
+ if err != nil {
+ c.Error(http.StatusUnprocessableEntity, "", err)
+ return
+ }
+ }
+
+ comments, err := db.GetCommentsByRepoIDSince(c.Repo.Repository.ID, since.Unix())
+ if err != nil {
+ c.ServerError("GetCommentsByRepoIDSince", err)
+ return
+ }
+
+ apiComments := make([]*api.Comment, len(comments))
+ for i := range comments {
+ apiComments[i] = comments[i].APIFormat()
+ }
+ c.JSONSuccess(&apiComments)
+}
+
+func CreateIssueComment(c *context.APIContext, form api.CreateIssueCommentOption) {
+ issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ c.ServerError("GetIssueByIndex", err)
+ return
+ }
+
+ comment, err := db.CreateIssueComment(c.User, c.Repo.Repository, issue, form.Body, nil)
+ if err != nil {
+ c.ServerError("CreateIssueComment", err)
+ return
+ }
+
+ c.JSON(http.StatusCreated, comment.APIFormat())
+}
+
+func EditIssueComment(c *context.APIContext, form api.EditIssueCommentOption) {
+ comment, err := db.GetCommentByID(c.ParamsInt64(":id"))
+ if err != nil {
+ c.NotFoundOrServerError("GetCommentByID", db.IsErrCommentNotExist, err)
+ return
+ }
+
+ if c.User.ID != comment.PosterID && !c.Repo.IsAdmin() {
+ c.Status(http.StatusForbidden)
+ return
+ } else if comment.Type != db.COMMENT_TYPE_COMMENT {
+ c.NoContent()
+ return
+ }
+
+ oldContent := comment.Content
+ comment.Content = form.Body
+ if err := db.UpdateComment(c.User, comment, oldContent); err != nil {
+ c.ServerError("UpdateComment", err)
+ return
+ }
+ c.JSONSuccess(comment.APIFormat())
+}
+
+func DeleteIssueComment(c *context.APIContext) {
+ comment, err := db.GetCommentByID(c.ParamsInt64(":id"))
+ if err != nil {
+ c.NotFoundOrServerError("GetCommentByID", db.IsErrCommentNotExist, err)
+ return
+ }
+
+ if c.User.ID != comment.PosterID && !c.Repo.IsAdmin() {
+ c.Status(http.StatusForbidden)
+ return
+ } else if comment.Type != db.COMMENT_TYPE_COMMENT {
+ c.NoContent()
+ return
+ }
+
+ if err = db.DeleteCommentByID(c.User, comment.ID); err != nil {
+ c.ServerError("DeleteCommentByID", err)
+ return
+ }
+ c.NoContent()
+}
diff --git a/internal/route/api/v1/repo/issue_label.go b/internal/route/api/v1/repo/issue_label.go
new file mode 100644
index 00000000..7c8b7982
--- /dev/null
+++ b/internal/route/api/v1/repo/issue_label.go
@@ -0,0 +1,131 @@
+// Copyright 2016 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+ "net/http"
+
+ api "github.com/gogs/go-gogs-client"
+
+ "gogs.io/gogs/internal/context"
+ "gogs.io/gogs/internal/db"
+ "gogs.io/gogs/internal/db/errors"
+)
+
+func ListIssueLabels(c *context.APIContext) {
+ issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
+ return
+ }
+
+ apiLabels := make([]*api.Label, len(issue.Labels))
+ for i := range issue.Labels {
+ apiLabels[i] = issue.Labels[i].APIFormat()
+ }
+ c.JSONSuccess(&apiLabels)
+}
+
+func AddIssueLabels(c *context.APIContext, form api.IssueLabelsOption) {
+ issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
+ return
+ }
+
+ labels, err := db.GetLabelsInRepoByIDs(c.Repo.Repository.ID, form.Labels)
+ if err != nil {
+ c.ServerError("GetLabelsInRepoByIDs", err)
+ return
+ }
+
+ if err = issue.AddLabels(c.User, labels); err != nil {
+ c.ServerError("AddLabels", err)
+ return
+ }
+
+ labels, err = db.GetLabelsByIssueID(issue.ID)
+ if err != nil {
+ c.ServerError("GetLabelsByIssueID", err)
+ return
+ }
+
+ apiLabels := make([]*api.Label, len(labels))
+ for i := range labels {
+ apiLabels[i] = issue.Labels[i].APIFormat()
+ }
+ c.JSONSuccess(&apiLabels)
+}
+
+func DeleteIssueLabel(c *context.APIContext) {
+ issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
+ return
+ }
+
+ label, err := db.GetLabelOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
+ if err != nil {
+ if db.IsErrLabelNotExist(err) {
+ c.Error(http.StatusUnprocessableEntity, "", err)
+ } else {
+ c.ServerError("GetLabelInRepoByID", err)
+ }
+ return
+ }
+
+ if err := db.DeleteIssueLabel(issue, label); err != nil {
+ c.ServerError("DeleteIssueLabel", err)
+ return
+ }
+
+ c.NoContent()
+}
+
+func ReplaceIssueLabels(c *context.APIContext, form api.IssueLabelsOption) {
+ issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
+ return
+ }
+
+ labels, err := db.GetLabelsInRepoByIDs(c.Repo.Repository.ID, form.Labels)
+ if err != nil {
+ c.ServerError("GetLabelsInRepoByIDs", err)
+ return
+ }
+
+ if err := issue.ReplaceLabels(labels); err != nil {
+ c.ServerError("ReplaceLabels", err)
+ return
+ }
+
+ labels, err = db.GetLabelsByIssueID(issue.ID)
+ if err != nil {
+ c.ServerError("GetLabelsByIssueID", err)
+ return
+ }
+
+ apiLabels := make([]*api.Label, len(labels))
+ for i := range labels {
+ apiLabels[i] = issue.Labels[i].APIFormat()
+ }
+ c.JSONSuccess(&apiLabels)
+}
+
+func ClearIssueLabels(c *context.APIContext) {
+ issue, err := db.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
+ if err != nil {
+ c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
+ return
+ }
+
+ if err := issue.ClearLabels(c.User); err != nil {
+ c.ServerError("ClearLabels", err)
+ return
+ }
+
+ c.NoContent()
+}
diff --git a/internal/route/api/v1/repo/key.go b/internal/route/api/v1/repo/key.go
new file mode 100644
index 00000000..d47d4b46
--- /dev/null
+++ b/internal/route/api/v1/repo/key.go
@@ -0,0 +1,114 @@
+// Copyright 2015 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+ "fmt"
+ convert2 "gogs.io/gogs/internal/route/api/v1/convert"
+
+ api "github.com/gogs/go-gogs-client"
+
+ "gogs.io/gogs/internal/context"
+ "gogs.io/gogs/internal/db"
+ "gogs.io/gogs/internal/setting"
+)
+
+func composeDeployKeysAPILink(repoPath string) string {
+ return setting.AppURL + "api/v1/repos/" + repoPath + "/keys/"
+}
+
+// https://github.com/gogs/go-gogs-client/wiki/Repositories-Deploy-Keys#list-deploy-keys
+func ListDeployKeys(c *context.APIContext) {
+ keys, err := db.ListDeployKeys(c.Repo.Repository.ID)
+ if err != nil {
+ c.Error(500, "ListDeployKeys", err)
+ return
+ }
+
+ apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name)
+ apiKeys := make([]*api.DeployKey, len(keys))
+ for i := range keys {
+ if err = keys[i].GetContent(); err != nil {
+ c.Error(500, "GetContent", err)
+ return
+ }
+ apiKeys[i] = convert2.ToDeployKey(apiLink, keys[i])
+ }
+
+ c.JSON(200, &apiKeys)
+}
+
+// https://github.com/gogs/go-gogs-client/wiki/Repositories-Deploy-Keys#get-a-deploy-key
+func GetDeployKey(c *context.APIContext) {
+ key, err := db.GetDeployKeyByID(c.ParamsInt64(":id"))
+ if err != nil {
+ if db.IsErrDeployKeyNotExist(err) {
+ c.Status(404)
+ } else {
+ c.Error(500, "GetDeployKeyByID", err)
+ }
+ return
+ }
+
+ if err = key.GetContent(); err != nil {
+ c.Error(500, "GetContent", err)
+ return
+ }
+
+ apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name)
+ c.JSON(200, convert2.ToDeployKey(apiLink, key))
+}
+
+func HandleCheckKeyStringError(c *context.APIContext, err error) {
+ if db.IsErrKeyUnableVerify(err) {
+ c.Error(422, "", "Unable to verify key content")
+ } else {
+ c.Error(422, "", fmt.Errorf("Invalid key content: %v", err))
+ }
+}
+
+func HandleAddKeyError(c *context.APIContext, err error) {
+ switch {
+ case db.IsErrKeyAlreadyExist(err):
+ c.Error(422, "", "Key content has been used as non-deploy key")
+ case db.IsErrKeyNameAlreadyUsed(err):
+ c.Error(422, "", "Key title has been used")
+ default:
+ c.Error(500, "AddKey", err)
+ }
+}
+
+// https://github.com/gogs/go-gogs-client/wiki/Repositories-Deploy-Keys#add-a-new-deploy-key
+func CreateDeployKey(c *context.APIContext, form api.CreateKeyOption) {
+ content, err := db.CheckPublicKeyString(form.Key)
+ if err != nil {
+ HandleCheckKeyStringError(c, err)
+ return
+ }
+
+ key, err := db.AddDeployKey(c.Repo.Repository.ID, form.Title, content)
+ if err != nil {
+ HandleAddKeyError(c, err)
+ return
+ }
+
+ key.Content = content
+ apiLink := composeDeployKeysAPILink(c.Repo.Owner.Name + "/" + c.Repo.Repository.Name)
+ c.JSON(201, convert2.ToDeployKey(apiLink, key))
+}
+
+// https://github.com/gogs/go-gogs-client/wiki/Repositories-Deploy-Keys#remove-a-deploy-key
+func DeleteDeploykey(c *context.APIContext) {
+ if err := db.DeleteDeployKey(c.User, c.ParamsInt64(":id")); err != nil {
+ if db.IsErrKeyAccessDenied(err) {
+ c.Error(403, "", "You do not have access to this key")
+ } else {
+ c.Error(500, "DeleteDeployKey", err)
+ }
+ return
+ }
+
+ c.Status(204)
+}
diff --git a/internal/route/api/v1/repo/label.go b/internal/route/api/v1/repo/label.go
new file mode 100644
index 00000000..9dd2d7d0
--- /dev/null
+++ b/internal/route/api/v1/repo/label.go
@@ -0,0 +1,89 @@
+// Copyright 2016 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+ "net/http"
+
+ "github.com/unknwon/com"
+
+ api "github.com/gogs/go-gogs-client"
+
+ "gogs.io/gogs/internal/context"
+ "gogs.io/gogs/internal/db"
+)
+
+func ListLabels(c *context.APIContext) {
+ labels, err := db.GetLabelsByRepoID(c.Repo.Repository.ID)
+ if err != nil {
+ c.ServerError("GetLabelsByRepoID", err)
+ return
+ }
+
+ apiLabels := make([]*api.Label, len(labels))
+ for i := range labels {
+ apiLabels[i] = labels[i].APIFormat()
+ }
+ c.JSONSuccess(&apiLabels)
+}
+
+func GetLabel(c *context.APIContext) {
+ var label *db.Label
+ var err error
+ idStr := c.Params(":id")
+ if id := com.StrTo(idStr).MustInt64(); id > 0 {
+ label, err = db.GetLabelOfRepoByID(c.Repo.Repository.ID, id)
+ } else {
+ label, err = db.GetLabelOfRepoByName(c.Repo.Repository.ID, idStr)
+ }
+ if err != nil {
+ c.NotFoundOrServerError("GetLabel", db.IsErrLabelNotExist, err)
+ return
+ }
+
+ c.JSONSuccess(label.APIFormat())
+}
+
+func CreateLabel(c *context.APIContext, form api.CreateLabelOption) {
+ label := &db.Label{
+ Name: form.Name,
+ Color: form.Color,
+ RepoID: c.Repo.Repository.ID,
+ }
+ if err := db.NewLabels(label); err != nil {
+ c.ServerError("NewLabel", err)
+ return
+ }
+ c.JSON(http.StatusCreated, label.APIFormat())
+}
+
+func EditLabel(c *context.APIContext, form api.EditLabelOption) {
+ label, err := db.GetLabelOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
+ if err != nil {
+ c.NotFoundOrServerError("GetLabelOfRepoByID", db.IsErrLabelNotExist, err)
+ return
+ }
+
+ if form.Name != nil {
+ label.Name = *form.Name
+ }
+ if form.Color != nil {
+ label.Color = *form.Color
+ }
+ if err := db.UpdateLabel(label); err != nil {
+ c.ServerError("UpdateLabel", err)
+ return
+ }
+ c.JSONSuccess(label.APIFormat())
+}
+
+func DeleteLabel(c *context.APIContext) {
+ if err := db.DeleteLabel(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil {
+ c.ServerError("DeleteLabel", err)
+ return
+ }
+
+ c.NoContent()
+}
diff --git a/internal/route/api/v1/repo/milestone.go b/internal/route/api/v1/repo/milestone.go
new file mode 100644
index 00000000..6f5fea17
--- /dev/null
+++ b/internal/route/api/v1/repo/milestone.go
@@ -0,0 +1,96 @@
+// Copyright 2016 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+ "net/http"
+ "time"
+
+ api "github.com/gogs/go-gogs-client"
+
+ "gogs.io/gogs/internal/context"
+ "gogs.io/gogs/internal/db"
+)
+
+func ListMilestones(c *context.APIContext) {
+ milestones, err := db.GetMilestonesByRepoID(c.Repo.Repository.ID)
+ if err != nil {
+ c.ServerError("GetMilestonesByRepoID", err)
+ return
+ }
+
+ apiMilestones := make([]*api.Milestone, len(milestones))
+ for i := range milestones {
+ apiMilestones[i] = milestones[i].APIFormat()
+ }
+ c.JSONSuccess(&apiMilestones)
+}
+
+func GetMilestone(c *context.APIContext) {
+ milestone, err := db.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
+ if err != nil {
+ c.NotFoundOrServerError("GetMilestoneByRepoID", db.IsErrMilestoneNotExist, err)
+ return
+ }
+ c.JSONSuccess(milestone.APIFormat())
+}
+
+func CreateMilestone(c *context.APIContext, form api.CreateMilestoneOption) {
+ if form.Deadline == nil {
+ defaultDeadline, _ := time.ParseInLocation("2006-01-02", "9999-12-31", time.Local)
+ form.Deadline = &defaultDeadline
+ }
+
+ milestone := &db.Milestone{
+ RepoID: c.Repo.Repository.ID,
+ Name: form.Title,
+ Content: form.Description,
+ Deadline: *form.Deadline,
+ }
+
+ if err := db.NewMilestone(milestone); err != nil {
+ c.ServerError("NewMilestone", err)
+ return
+ }
+ c.JSON(http.StatusCreated, milestone.APIFormat())
+}
+
+func EditMilestone(c *context.APIContext, form api.EditMilestoneOption) {
+ milestone, err := db.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
+ if err != nil {
+ c.NotFoundOrServerError("GetMilestoneByRepoID", db.IsErrMilestoneNotExist, err)
+ return
+ }
+
+ if len(form.Title) > 0 {
+ milestone.Name = form.Title
+ }
+ if form.Description != nil {
+ milestone.Content = *form.Description
+ }
+ if form.Deadline != nil && !form.Deadline.IsZero() {
+ milestone.Deadline = *form.Deadline
+ }
+
+ if form.State != nil {
+ if err = milestone.ChangeStatus(api.STATE_CLOSED == api.StateType(*form.State)); err != nil {
+ c.ServerError("ChangeStatus", err)
+ return
+ }
+ } else if err = db.UpdateMilestone(milestone); err != nil {
+ c.ServerError("UpdateMilestone", err)
+ return
+ }
+
+ c.JSONSuccess(milestone.APIFormat())
+}
+
+func DeleteMilestone(c *context.APIContext) {
+ if err := db.DeleteMilestoneOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil {
+ c.ServerError("DeleteMilestoneByRepoID", err)
+ return
+ }
+ c.NoContent()
+}
diff --git a/internal/route/api/v1/repo/repo.go b/internal/route/api/v1/repo/repo.go
new file mode 100644
index 00000000..096096fb
--- /dev/null
+++ b/internal/route/api/v1/repo/repo.go
@@ -0,0 +1,407 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+ "fmt"
+ convert2 "gogs.io/gogs/internal/route/api/v1/convert"
+ "net/http"
+ "path"
+
+ log "gopkg.in/clog.v1"
+
+ api "github.com/gogs/go-gogs-client"
+
+ "gogs.io/gogs/internal/context"
+ "gogs.io/gogs/internal/db"
+ "gogs.io/gogs/internal/db/errors"
+ "gogs.io/gogs/internal/form"
+ "gogs.io/gogs/internal/setting"
+)
+
+func Search(c *context.APIContext) {
+ opts := &db.SearchRepoOptions{
+ Keyword: path.Base(c.Query("q")),
+ OwnerID: c.QueryInt64("uid"),
+ PageSize: convert2.ToCorrectPageSize(c.QueryInt("limit")),
+ Page: c.QueryInt("page"),
+ }
+
+ // Check visibility.
+ if c.IsLogged && opts.OwnerID > 0 {
+ if c.User.ID == opts.OwnerID {
+ opts.Private = true
+ } else {
+ u, err := db.GetUserByID(opts.OwnerID)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, map[string]interface{}{
+ "ok": false,
+ "error": err.Error(),
+ })
+ return
+ }
+ if u.IsOrganization() && u.IsOwnedBy(c.User.ID) {
+ opts.Private = true
+ }
+ // FIXME: how about collaborators?
+ }
+ }
+
+ repos, count, err := db.SearchRepositoryByName(opts)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, map[string]interface{}{
+ "ok": false,
+ "error": err.Error(),
+ })
+ return
+ }
+
+ if err = db.RepositoryList(repos).LoadAttributes(); err != nil {
+ c.JSON(http.StatusInternalServerError, map[string]interface{}{
+ "ok": false,
+ "error": err.Error(),
+ })
+ return
+ }
+
+ results := make([]*api.Repository, len(repos))
+ for i := range repos {
+ results[i] = repos[i].APIFormat(nil)
+ }
+
+ c.SetLinkHeader(int(count), opts.PageSize)
+ c.JSONSuccess(map[string]interface{}{
+ "ok": true,
+ "data": results,
+ })
+}
+
+func listUserRepositories(c *context.APIContext, username string) {
+ user, err := db.GetUserByName(username)
+ if err != nil {
+ c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
+ return
+ }
+
+ // Only list public repositories if user requests someone else's repository list,
+ // or an organization isn't a member of.
+ var ownRepos []*db.Repository
+ if user.IsOrganization() {
+ ownRepos, _, err = user.GetUserRepositories(c.User.ID, 1, user.NumRepos)
+ } else {
+ ownRepos, err = db.GetUserRepositories(&db.UserRepoOptions{
+ UserID: user.ID,
+ Private: c.User.ID == user.ID,
+ Page: 1,
+ PageSize: user.NumRepos,
+ })
+ }
+ if err != nil {
+ c.ServerError("GetUserRepositories", err)
+ return
+ }
+
+ if err = db.RepositoryList(ownRepos).LoadAttributes(); err != nil {
+ c.ServerError("LoadAttributes(ownRepos)", err)
+ return
+ }
+
+ // Early return for querying other user's repositories
+ if c.User.ID != user.ID {
+ repos := make([]*api.Repository, len(ownRepos))
+ for i := range ownRepos {
+ repos[i] = ownRepos[i].APIFormat(&api.Permission{true, true, true})
+ }
+ c.JSONSuccess(&repos)
+ return
+ }
+
+ accessibleRepos, err := user.GetRepositoryAccesses()
+ if err != nil {
+ c.ServerError("GetRepositoryAccesses", err)
+ return
+ }
+
+ numOwnRepos := len(ownRepos)
+ repos := make([]*api.Repository, numOwnRepos+len(accessibleRepos))
+ for i := range ownRepos {
+ repos[i] = ownRepos[i].APIFormat(&api.Permission{true, true, true})
+ }
+
+ i := numOwnRepos
+ for repo, access := range accessibleRepos {
+ repos[i] = repo.APIFormat(&api.Permission{
+ Admin: access >= db.ACCESS_MODE_ADMIN,
+ Push: access >= db.ACCESS_MODE_WRITE,
+ Pull: true,
+ })
+ i++
+ }
+
+ c.JSONSuccess(&repos)
+}
+
+func ListMyRepos(c *context.APIContext) {
+ listUserRepositories(c, c.User.Name)
+}
+
+func ListUserRepositories(c *context.APIContext) {
+ listUserRepositories(c, c.Params(":username"))
+}
+
+func ListOrgRepositories(c *context.APIContext) {
+ listUserRepositories(c, c.Params(":org"))
+}
+
+func CreateUserRepo(c *context.APIContext, owner *db.User, opt api.CreateRepoOption) {
+ repo, err := db.CreateRepository(c.User, owner, db.CreateRepoOptions{
+ Name: opt.Name,
+ Description: opt.Description,
+ Gitignores: opt.Gitignores,
+ License: opt.License,
+ Readme: opt.Readme,
+ IsPrivate: opt.Private,
+ AutoInit: opt.AutoInit,
+ })
+ if err != nil {
+ if db.IsErrRepoAlreadyExist(err) ||
+ db.IsErrNameReserved(err) ||
+ db.IsErrNamePatternNotAllowed(err) {
+ c.Error(http.StatusUnprocessableEntity, "", err)
+ } else {
+ if repo != nil {
+ if err = db.DeleteRepository(c.User.ID, repo.ID); err != nil {
+ log.Error(2, "DeleteRepository: %v", err)
+ }
+ }
+ c.ServerError("CreateRepository", err)
+ }
+ return
+ }
+
+ c.JSON(201, repo.APIFormat(&api.Permission{true, true, true}))
+}
+
+func Create(c *context.APIContext, opt api.CreateRepoOption) {
+ // Shouldn't reach this condition, but just in case.
+ if c.User.IsOrganization() {
+ c.Error(http.StatusUnprocessableEntity, "", "not allowed creating repository for organization")
+ return
+ }
+ CreateUserRepo(c, c.User, opt)
+}
+
+func CreateOrgRepo(c *context.APIContext, opt api.CreateRepoOption) {
+ org, err := db.GetOrgByName(c.Params(":org"))
+ if err != nil {
+ c.NotFoundOrServerError("GetOrgByName", errors.IsUserNotExist, err)
+ return
+ }
+
+ if !org.IsOwnedBy(c.User.ID) {
+ c.Error(http.StatusForbidden, "", "given user is not owner of organization")
+ return
+ }
+ CreateUserRepo(c, org, opt)
+}
+
+func Migrate(c *context.APIContext, f form.MigrateRepo) {
+ ctxUser := c.User
+ // Not equal means context user is an organization,
+ // or is another user/organization if current user is admin.
+ if f.Uid != ctxUser.ID {
+ org, err := db.GetUserByID(f.Uid)
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(http.StatusUnprocessableEntity, "", err)
+ } else {
+ c.Error(http.StatusInternalServerError, "GetUserByID", err)
+ }
+ return
+ } else if !org.IsOrganization() && !c.User.IsAdmin {
+ c.Error(http.StatusForbidden, "", "given user is not an organization")
+ return
+ }
+ ctxUser = org
+ }
+
+ if c.HasError() {
+ c.Error(http.StatusUnprocessableEntity, "", c.GetErrMsg())
+ return
+ }
+
+ if ctxUser.IsOrganization() && !c.User.IsAdmin {
+ // Check ownership of organization.
+ if !ctxUser.IsOwnedBy(c.User.ID) {
+ c.Error(http.StatusForbidden, "", "Given user is not owner of organization")
+ return
+ }
+ }
+
+ remoteAddr, err := f.ParseRemoteAddr(c.User)
+ if err != nil {
+ if db.IsErrInvalidCloneAddr(err) {
+ addrErr := err.(db.ErrInvalidCloneAddr)
+ switch {
+ case addrErr.IsURLError:
+ c.Error(http.StatusUnprocessableEntity, "", err)
+ case addrErr.IsPermissionDenied:
+ c.Error(http.StatusUnprocessableEntity, "", "you are not allowed to import local repositories")
+ case addrErr.IsInvalidPath:
+ c.Error(http.StatusUnprocessableEntity, "", "invalid local path, it does not exist or not a directory")
+ default:
+ c.ServerError("ParseRemoteAddr", fmt.Errorf("unknown error type (ErrInvalidCloneAddr): %v", err))
+ }
+ } else {
+ c.ServerError("ParseRemoteAddr", err)
+ }
+ return
+ }
+
+ repo, err := db.MigrateRepository(c.User, ctxUser, db.MigrateRepoOptions{
+ Name: f.RepoName,
+ Description: f.Description,
+ IsPrivate: f.Private || setting.Repository.ForcePrivate,
+ IsMirror: f.Mirror,
+ RemoteAddr: remoteAddr,
+ })
+ if err != nil {
+ if repo != nil {
+ if errDelete := db.DeleteRepository(ctxUser.ID, repo.ID); errDelete != nil {
+ log.Error(2, "DeleteRepository: %v", errDelete)
+ }
+ }
+
+ if errors.IsReachLimitOfRepo(err) {
+ c.Error(http.StatusUnprocessableEntity, "", err)
+ } else {
+ c.ServerError("MigrateRepository", errors.New(db.HandleMirrorCredentials(err.Error(), true)))
+ }
+ return
+ }
+
+ log.Trace("Repository migrated: %s/%s", ctxUser.Name, f.RepoName)
+ c.JSON(201, repo.APIFormat(&api.Permission{true, true, true}))
+}
+
+// FIXME: inject in the handler chain
+func parseOwnerAndRepo(c *context.APIContext) (*db.User, *db.Repository) {
+ owner, err := db.GetUserByName(c.Params(":username"))
+ if err != nil {
+ if errors.IsUserNotExist(err) {
+ c.Error(http.StatusUnprocessableEntity, "", err)
+ } else {
+ c.ServerError("GetUserByName", err)
+ }
+ return nil, nil
+ }
+
+ repo, err := db.GetRepositoryByName(owner.ID, c.Params(":reponame"))
+ if err != nil {
+ c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err)
+ return nil, nil
+ }
+
+ return owner, repo
+}
+
+func Get(c *context.APIContext) {
+ _, repo := parseOwnerAndRepo(c)
+ if c.Written() {
+ return
+ }
+
+ c.JSONSuccess(repo.APIFormat(&api.Permission{
+ Admin: c.Repo.IsAdmin(),
+ Push: c.Repo.IsWriter(),
+ Pull: true,
+ }))
+}
+
+func Delete(c *context.APIContext) {
+ owner, repo := parseOwnerAndRepo(c)
+ if c.Written() {
+ return
+ }
+
+ if owner.IsOrganization() && !owner.IsOwnedBy(c.User.ID) {
+ c.Error(http.StatusForbidden, "", "given user is not owner of organization")
+ return
+ }
+
+ if err := db.DeleteRepository(owner.ID, repo.ID); err != nil {
+ c.ServerError("DeleteRepository", err)
+ return
+ }
+
+ log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
+ c.NoContent()
+}
+
+func ListForks(c *context.APIContext) {
+ forks, err := c.Repo.Repository.GetForks()
+ if err != nil {
+ c.ServerError("GetForks", err)
+ return
+ }
+
+ apiForks := make([]*api.Repository, len(forks))
+ for i := range forks {
+ if err := forks[i].GetOwner(); err != nil {
+ c.ServerError("GetOwner", err)
+ return
+ }
+ apiForks[i] = forks[i].APIFormat(&api.Permission{
+ Admin: c.User.IsAdminOfRepo(forks[i]),
+ Push: c.User.IsWriterOfRepo(forks[i]),
+ Pull: true,
+ })
+ }
+
+ c.JSONSuccess(&apiForks)
+}
+
+func IssueTracker(c *context.APIContext, form api.EditIssueTrackerOption) {
+ _, repo := parseOwnerAndRepo(c)
+ if c.Written() {
+ return
+ }
+
+ if form.EnableIssues != nil {
+ repo.EnableIssues = *form.EnableIssues
+ }
+ if form.EnableExternalTracker != nil {
+ repo.EnableExternalTracker = *form.EnableExternalTracker
+ }
+ if form.ExternalTrackerURL != nil {
+ repo.ExternalTrackerURL = *form.ExternalTrackerURL
+ }
+ if form.TrackerURLFormat != nil {
+ repo.ExternalTrackerFormat = *form.TrackerURLFormat
+ }
+ if form.TrackerIssueStyle != nil {
+ repo.ExternalTrackerStyle = *form.TrackerIssueStyle
+ }
+
+ if err := db.UpdateRepository(repo, false); err != nil {
+ c.ServerError("UpdateRepository", err)
+ return
+ }
+
+ c.NoContent()
+}
+
+func MirrorSync(c *context.APIContext) {
+ _, repo := parseOwnerAndRepo(c)
+ if c.Written() {
+ return
+ } else if !repo.IsMirror {
+ c.NotFound()
+ return
+ }
+
+ go db.MirrorQueue.Add(repo.ID)
+ c.Status(http.StatusAccepted)
+}