]> source.dussan.org Git - gitea.git/commitdiff
Additional API support for labels (#3290)
authorlstahlman <luke.stahlman@gmail.com>
Wed, 3 Aug 2016 16:24:16 +0000 (09:24 -0700)
committer无闻 <u@gogs.io>
Wed, 3 Aug 2016 16:24:16 +0000 (09:24 -0700)
* Add API support for labels.

* Error handling for adding/replacing multiple issue labels

* Revisions to function names and error handling. Use issue.ClearLabels in replace/clear functions

* Additional code cleanup

models/error.go
routers/api/v1/api.go
routers/api/v1/convert/convert.go
routers/api/v1/repo/issue_label.go [new file with mode: 0644]
routers/api/v1/repo/label.go [new file with mode: 0644]

index a6cf31218cba162814ae67048bc0f04bd10e18d9..a6c54ab088f85085f61636b92697ff189da1f5eb 100644 (file)
@@ -5,6 +5,7 @@
 package models
 
 import (
+       "bytes"
        "fmt"
 )
 
@@ -34,6 +35,30 @@ func (err ErrNamePatternNotAllowed) Error() string {
        return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern)
 }
 
+type ErrMultipleErrors struct {
+       Errors []error
+}
+
+func IsErrMultipleErrors(err error) bool {
+       _, ok := err.(ErrMultipleErrors)
+       return ok
+}
+
+func (err ErrMultipleErrors) Error() string {
+       var message bytes.Buffer
+
+       message.WriteString("Multiple errors encountered: ")
+
+       for i := range err.Errors {
+               message.WriteString(err.Errors[i].Error())
+               if i < len(err.Errors)-1 {
+                       message.WriteString("; ")
+               }
+       }
+
+       return message.String()
+}
+
 //  ____ ___
 // |    |   \______ ___________
 // |    |   /  ___// __ \_  __ \
@@ -545,6 +570,20 @@ func (err ErrLabelNotExist) Error() string {
        return fmt.Sprintf("label does not exist [id: %d]", err.ID)
 }
 
+type ErrLabelNotValidForRepository struct {
+       ID     int64
+       RepoID int64
+}
+
+func IsErrLabelNotValidForRepository(err error) bool {
+       _, ok := err.(ErrLabelNotValidForRepository)
+       return ok
+}
+
+func (err ErrLabelNotValidForRepository) Error() string {
+       return fmt.Sprintf("label is not valid for repository [label_id: %d, repo_id: %d]", err.ID, err.RepoID)
+}
+
 //    _____  .__.__                   __
 //   /     \ |__|  |   ____   _______/  |_  ____   ____   ____
 //  /  \ /  \|  |  | _/ __ \ /  ___/\   __\/  _ \ /    \_/ __ \
index a0af20074970eb6f348565e3e9b859a33339c019..80131af6cdb1a85c22e7af6b2d7a71faa2f74f6b 100644 (file)
@@ -241,7 +241,23 @@ func RegisterRoutes(m *macaron.Macaron) {
                                })
                                m.Group("/issues", func() {
                                        m.Combo("").Get(repo.ListIssues).Post(bind(api.CreateIssueOption{}), repo.CreateIssue)
-                                       m.Combo("/:index").Get(repo.GetIssue).Patch(bind(api.EditIssueOption{}), repo.EditIssue)
+                                       m.Group("/:index", func() {
+                                               m.Combo("").Get(repo.GetIssue).Patch(bind(api.EditIssueOption{}), repo.EditIssue)
+                                               m.Group("/labels", func() {
+                                                       m.Combo("").Get(repo.GetIssueLabels).
+                                                               Post(bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
+                                                               Put(bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
+                                                               Delete(repo.ClearIssueLabels)
+                                                       m.Delete("/:id", repo.DeleteIssueLabel)
+                                               })
+
+                                       })
+                               })
+                               m.Group("/labels", func() {
+                                       m.Combo("").Get(repo.ListLabels).
+                                               Post(bind(api.LabelOption{}), repo.CreateLabel)
+                                       m.Combo("/:id").Get(repo.GetLabel).Patch(bind(api.LabelOption{}), repo.EditLabel).
+                                               Delete(repo.DeleteLabel)
                                })
                        }, RepoAssignment())
                }, ReqToken())
index 596b450f27c2f4493fe4adc591a149221ff79e02..0dc73fcd6774c855a6a34a71adfb30efbfeb4c56 100644 (file)
@@ -129,6 +129,7 @@ func ToDeployKey(apiLink string, key *models.DeployKey) *api.DeployKey {
 
 func ToLabel(label *models.Label) *api.Label {
        return &api.Label{
+               ID:    label.ID,
                Name:  label.Name,
                Color: label.Color,
        }
diff --git a/routers/api/v1/repo/issue_label.go b/routers/api/v1/repo/issue_label.go
new file mode 100644 (file)
index 0000000..ea697b2
--- /dev/null
@@ -0,0 +1,219 @@
+// 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/gogits/go-gogs-client"
+
+       "github.com/gogits/gogs/models"
+       "github.com/gogits/gogs/modules/context"
+       "github.com/gogits/gogs/routers/api/v1/convert"
+)
+
+func GetIssueLabels(ctx *context.APIContext) {
+       issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
+       if err != nil {
+               if models.IsErrIssueNotExist(err) {
+                       ctx.Status(404)
+               } else {
+                       ctx.Error(500, "GetIssueByIndex", err)
+               }
+               return
+       }
+
+       apiLabels := make([]*api.Label, len(issue.Labels))
+       for i := range issue.Labels {
+               apiLabels[i] = convert.ToLabel(issue.Labels[i])
+       }
+
+       ctx.JSON(200, &apiLabels)
+}
+
+func AddIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) {
+       if !ctx.Repo.IsWriter() {
+               ctx.Status(403)
+               return
+       }
+
+       issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
+       if err != nil {
+               if models.IsErrIssueNotExist(err) {
+                       ctx.Status(404)
+               } else {
+                       ctx.Error(500, "GetIssueByIndex", err)
+               }
+               return
+       }
+
+       var labels []*models.Label
+       if labels, err = filterLabelsByRepoID(form.Labels, issue.RepoID); err != nil {
+               ctx.Error(400, "filterLabelsByRepoID", err)
+               return
+       }
+
+       for i := range labels {
+               if !models.HasIssueLabel(issue.ID, labels[i].ID) {
+                       if err := models.NewIssueLabel(issue, labels[i]); err != nil {
+                               ctx.Error(500, "NewIssueLabel", err)
+                               return
+                       }
+               }
+       }
+
+       // Refresh issue to get the updated list of labels from the DB
+       issue, err = models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
+       if err != nil {
+               if models.IsErrIssueNotExist(err) {
+                       ctx.Status(404)
+               } else {
+                       ctx.Error(500, "GetIssueByIndex", err)
+               }
+               return
+       }
+
+       apiLabels := make([]*api.Label, len(issue.Labels))
+       for i := range issue.Labels {
+               apiLabels[i] = convert.ToLabel(issue.Labels[i])
+       }
+
+       ctx.JSON(200, &apiLabels)
+}
+
+func ReplaceIssueLabels(ctx *context.APIContext, form api.IssueLabelsOption) {
+       if !ctx.Repo.IsWriter() {
+               ctx.Status(403)
+               return
+       }
+
+       issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
+       if err != nil {
+               if models.IsErrIssueNotExist(err) {
+                       ctx.Status(404)
+               } else {
+                       ctx.Error(500, "GetIssueByIndex", err)
+               }
+               return
+       }
+
+       var labels []*models.Label
+       if labels, err = filterLabelsByRepoID(form.Labels, issue.RepoID); err != nil {
+               ctx.Error(400, "filterLabelsByRepoID", err)
+               return
+       }
+
+       if err := issue.ClearLabels(); err != nil {
+               ctx.Error(500, "ClearLabels", err)
+               return
+       }
+
+       for i := range labels {
+               if err := models.NewIssueLabel(issue, labels[i]); err != nil {
+                       ctx.Error(500, "NewIssueLabel", err)
+                       return
+               }
+       }
+
+       // Refresh issue to get the updated list of labels from the DB
+       issue, err = models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
+       if err != nil {
+               if models.IsErrIssueNotExist(err) {
+                       ctx.Status(404)
+               } else {
+                       ctx.Error(500, "GetIssueByIndex", err)
+               }
+               return
+       }
+
+       apiLabels := make([]*api.Label, len(issue.Labels))
+       for i := range issue.Labels {
+               apiLabels[i] = convert.ToLabel(issue.Labels[i])
+       }
+
+       ctx.JSON(200, &apiLabels)
+}
+
+func DeleteIssueLabel(ctx *context.APIContext) {
+       if !ctx.Repo.IsWriter() {
+               ctx.Status(403)
+               return
+       }
+
+       issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
+       if err != nil {
+               if models.IsErrIssueNotExist(err) {
+                       ctx.Status(404)
+               } else {
+                       ctx.Error(500, "GetIssueByIndex", err)
+               }
+               return
+       }
+
+       label, err := models.GetLabelByID(ctx.ParamsInt64(":id"))
+       if err != nil {
+               if models.IsErrLabelNotExist(err) {
+                       ctx.Status(400)
+               } else {
+                       ctx.Error(500, "GetLabelByID", err)
+               }
+               return
+       }
+
+       if err := models.DeleteIssueLabel(issue, label); err != nil {
+               ctx.Error(500, "DeleteIssueLabel", err)
+               return
+       }
+
+       ctx.Status(204)
+}
+
+func ClearIssueLabels(ctx *context.APIContext) {
+       if !ctx.Repo.IsWriter() {
+               ctx.Status(403)
+               return
+       }
+
+       issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
+       if err != nil {
+               if models.IsErrIssueNotExist(err) {
+                       ctx.Status(404)
+               } else {
+                       ctx.Error(500, "GetIssueByIndex", err)
+               }
+               return
+       }
+
+       if err := issue.ClearLabels(); err != nil {
+               ctx.Error(500, "ClearLabels", err)
+               return
+       }
+
+       ctx.Status(204)
+}
+
+func filterLabelsByRepoID(labelIDs []int64, repoID int64) ([]*models.Label, error) {
+       labels := make([]*models.Label, 0, len(labelIDs))
+       errors := make([]error, 0, len(labelIDs))
+
+       for i := range labelIDs {
+               label, err := models.GetLabelByID(labelIDs[i])
+               if err != nil {
+                       errors = append(errors, err)
+               } else if label.RepoID != repoID {
+                       errors = append(errors, models.ErrLabelNotValidForRepository{label.ID, repoID})
+               } else {
+                       labels = append(labels, label)
+               }
+       }
+
+       errorCount := len(errors)
+
+       if errorCount == 1 {
+               return labels, errors[0]
+       } else if errorCount > 1 {
+               return labels, models.ErrMultipleErrors{errors}
+       }
+
+       return labels, nil
+}
diff --git a/routers/api/v1/repo/label.go b/routers/api/v1/repo/label.go
new file mode 100644 (file)
index 0000000..92e199a
--- /dev/null
@@ -0,0 +1,123 @@
+// 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/gogits/go-gogs-client"
+
+       "github.com/gogits/gogs/models"
+       "github.com/gogits/gogs/modules/context"
+       "github.com/gogits/gogs/modules/log"
+       "github.com/gogits/gogs/routers/api/v1/convert"
+)
+
+func ListLabels(ctx *context.APIContext) {
+       labels, err := models.GetLabelsByRepoID(ctx.Repo.Repository.ID)
+       if err != nil {
+               ctx.Error(500, "Labels", err)
+               return
+       }
+
+       apiLabels := make([]*api.Label, len(labels))
+       for i := range labels {
+               apiLabels[i] = convert.ToLabel(labels[i])
+       }
+
+       ctx.JSON(200, &apiLabels)
+}
+
+func GetLabel(ctx *context.APIContext) {
+       label, err := models.GetLabelByID(ctx.ParamsInt64(":id"))
+       if err != nil {
+               if models.IsErrLabelNotExist(err) {
+                       ctx.Status(404)
+               } else {
+                       ctx.Error(500, "GetLabelByID", err)
+               }
+               return
+       }
+
+       ctx.JSON(200, convert.ToLabel(label))
+}
+
+func CreateLabel(ctx *context.APIContext, form api.LabelOption) {
+       if !ctx.Repo.IsWriter() {
+               ctx.Status(403)
+               return
+       }
+
+       label := &models.Label{
+               Name:   form.Name,
+               Color:  form.Color,
+               RepoID: ctx.Repo.Repository.ID,
+       }
+       err := models.NewLabel(label)
+       if err != nil {
+               ctx.Error(500, "NewLabel", err)
+               return
+       }
+
+       label, err = models.GetLabelByID(label.ID)
+       if err != nil {
+               ctx.Error(500, "GetLabelByID", err)
+               return
+       }
+       ctx.JSON(201, convert.ToLabel(label))
+}
+
+func EditLabel(ctx *context.APIContext, form api.LabelOption) {
+       if !ctx.Repo.IsWriter() {
+               ctx.Status(403)
+               return
+       }
+
+       label, err := models.GetLabelByID(ctx.ParamsInt64(":id"))
+       if err != nil {
+               if models.IsErrLabelNotExist(err) {
+                       ctx.Status(404)
+               } else {
+                       ctx.Error(500, "GetLabelByID", err)
+               }
+               return
+       }
+
+       if len(form.Name) > 0 {
+               label.Name = form.Name
+       }
+       if len(form.Color) > 0 {
+               label.Color = form.Color
+       }
+
+       if err := models.UpdateLabel(label); err != nil {
+               ctx.Handle(500, "UpdateLabel", err)
+               return
+       }
+       ctx.JSON(200, convert.ToLabel(label))
+}
+
+func DeleteLabel(ctx *context.APIContext) {
+       if !ctx.Repo.IsWriter() {
+               ctx.Status(403)
+               return
+       }
+
+       label, err := models.GetLabelByID(ctx.ParamsInt64(":id"))
+       if err != nil {
+               if models.IsErrLabelNotExist(err) {
+                       ctx.Status(404)
+               } else {
+                       ctx.Error(500, "GetLabelByID", err)
+               }
+               return
+       }
+
+       if err := models.DeleteLabel(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")); err != nil {
+               ctx.Error(500, "DeleteLabel", err)
+               return
+       }
+
+       log.Trace("Label deleted: %s %s", label.ID, label.Name)
+       ctx.Status(204)
+}