diff options
author | Unknwon <u@gogs.io> | 2019-10-24 01:51:46 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-24 01:51:46 -0700 |
commit | 01c8df01ec0608f1f25b2f1444adabb98fa5ee8a (patch) | |
tree | f8a7e5dd8d2a8c51e1ce2cabb9d33571a93314dd /internal/route/api/v1/repo | |
parent | 613139e7bef81d3573e7988a47eb6765f3de347a (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.go | 55 | ||||
-rw-r--r-- | internal/route/api/v1/repo/collaborators.go | 90 | ||||
-rw-r--r-- | internal/route/api/v1/repo/commits.go | 138 | ||||
-rw-r--r-- | internal/route/api/v1/repo/file.go | 62 | ||||
-rw-r--r-- | internal/route/api/v1/repo/hook.go | 185 | ||||
-rw-r--r-- | internal/route/api/v1/repo/issue.go | 194 | ||||
-rw-r--r-- | internal/route/api/v1/repo/issue_comment.go | 131 | ||||
-rw-r--r-- | internal/route/api/v1/repo/issue_label.go | 131 | ||||
-rw-r--r-- | internal/route/api/v1/repo/key.go | 114 | ||||
-rw-r--r-- | internal/route/api/v1/repo/label.go | 89 | ||||
-rw-r--r-- | internal/route/api/v1/repo/milestone.go | 96 | ||||
-rw-r--r-- | internal/route/api/v1/repo/repo.go | 407 |
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) +} |