mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 11:35:03 +01:00 
			
		
		
		
	Additional API support for labels (#3290)
* 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
This commit is contained in:
		
							parent
							
								
									b1133c9934
								
							
						
					
					
						commit
						2eeb0ec9b0
					
				| @ -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) | ||||
| } | ||||
| 
 | ||||
| //    _____  .__.__                   __ | ||||
| //   /     \ |__|  |   ____   _______/  |_  ____   ____   ____ | ||||
| //  /  \ /  \|  |  | _/ __ \ /  ___/\   __\/  _ \ /    \_/ __ \ | ||||
|  | ||||
| @ -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()) | ||||
|  | ||||
| @ -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, | ||||
| 	} | ||||
|  | ||||
							
								
								
									
										219
									
								
								routers/api/v1/repo/issue_label.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								routers/api/v1/repo/issue_label.go
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| } | ||||
							
								
								
									
										123
									
								
								routers/api/v1/repo/label.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								routers/api/v1/repo/label.go
									
									
									
									
									
										Normal file
									
								
							| @ -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) | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user