From 51578d64188a7077848cb60d3ead8e818637ab59 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Fri, 10 Sep 2021 18:03:16 +0200
Subject: [PATCH] Calculate label URL on API  (#16186)

close #8028
---
 modules/convert/issue.go           | 33 +++++++++++++++++++++++++-----
 modules/convert/issue_test.go      |  6 +++++-
 routers/api/v1/org/label.go        | 10 +++++----
 routers/api/v1/repo/issue_label.go |  6 +++---
 routers/api/v1/repo/label.go       | 10 +++++----
 5 files changed, 48 insertions(+), 17 deletions(-)

diff --git a/modules/convert/issue.go b/modules/convert/issue.go
index da09aeaca4..3974d460e0 100644
--- a/modules/convert/issue.go
+++ b/modules/convert/issue.go
@@ -5,9 +5,12 @@
 package convert
 
 import (
+	"fmt"
 	"strings"
 
 	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
 )
 
@@ -25,6 +28,9 @@ func ToAPIIssue(issue *models.Issue) *api.Issue {
 	if err := issue.LoadRepo(); err != nil {
 		return &api.Issue{}
 	}
+	if err := issue.Repo.GetOwner(); err != nil {
+		return &api.Issue{}
+	}
 
 	apiIssue := &api.Issue{
 		ID:       issue.ID,
@@ -35,7 +41,7 @@ func ToAPIIssue(issue *models.Issue) *api.Issue {
 		Title:    issue.Title,
 		Body:     issue.Content,
 		Ref:      issue.Ref,
-		Labels:   ToLabelList(issue.Labels),
+		Labels:   ToLabelList(issue.Labels, issue.Repo, issue.Repo.Owner),
 		State:    issue.State(),
 		IsLocked: issue.IsLocked,
 		Comments: issue.NumComments,
@@ -168,20 +174,37 @@ func ToTrackedTimeList(tl models.TrackedTimeList) api.TrackedTimeList {
 }
 
 // ToLabel converts Label to API format
-func ToLabel(label *models.Label) *api.Label {
-	return &api.Label{
+func ToLabel(label *models.Label, repo *models.Repository, org *models.User) *api.Label {
+	result := &api.Label{
 		ID:          label.ID,
 		Name:        label.Name,
 		Color:       strings.TrimLeft(label.Color, "#"),
 		Description: label.Description,
 	}
+
+	// calculate URL
+	if label.BelongsToRepo() && repo != nil {
+		if repo != nil {
+			result.URL = fmt.Sprintf("%s/labels/%d", repo.APIURL(), label.ID)
+		} else {
+			log.Error("ToLabel did not get repo to calculate url for label with id '%d'", label.ID)
+		}
+	} else { // BelongsToOrg
+		if org != nil {
+			result.URL = fmt.Sprintf("%sapi/v1/orgs/%s/labels/%d", setting.AppURL, org.Name, label.ID)
+		} else {
+			log.Error("ToLabel did not get org to calculate url for label with id '%d'", label.ID)
+		}
+	}
+
+	return result
 }
 
 // ToLabelList converts list of Label to API format
-func ToLabelList(labels []*models.Label) []*api.Label {
+func ToLabelList(labels []*models.Label, repo *models.Repository, org *models.User) []*api.Label {
 	result := make([]*api.Label, len(labels))
 	for i := range labels {
-		result[i] = ToLabel(labels[i])
+		result[i] = ToLabel(labels[i], repo, org)
 	}
 	return result
 }
diff --git a/modules/convert/issue_test.go b/modules/convert/issue_test.go
index 2f8f56e99a..f3c5b50c8c 100644
--- a/modules/convert/issue_test.go
+++ b/modules/convert/issue_test.go
@@ -5,10 +5,12 @@
 package convert
 
 import (
+	"fmt"
 	"testing"
 	"time"
 
 	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/timeutil"
 
@@ -18,11 +20,13 @@ import (
 func TestLabel_ToLabel(t *testing.T) {
 	assert.NoError(t, models.PrepareTestDatabase())
 	label := models.AssertExistsAndLoadBean(t, &models.Label{ID: 1}).(*models.Label)
+	repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: label.RepoID}).(*models.Repository)
 	assert.Equal(t, &api.Label{
 		ID:    label.ID,
 		Name:  label.Name,
 		Color: "abcdef",
-	}, ToLabel(label))
+		URL:   fmt.Sprintf("%sapi/v1/repos/user2/repo1/labels/%d", setting.AppURL, label.ID),
+	}, ToLabel(label, repo, nil))
 }
 
 func TestMilestone_APIFormat(t *testing.T) {
diff --git a/routers/api/v1/org/label.go b/routers/api/v1/org/label.go
index 09acb0bf04..b375284189 100644
--- a/routers/api/v1/org/label.go
+++ b/routers/api/v1/org/label.go
@@ -56,7 +56,7 @@ func ListLabels(ctx *context.APIContext) {
 	}
 
 	ctx.SetTotalCountHeader(count)
-	ctx.JSON(http.StatusOK, convert.ToLabelList(labels))
+	ctx.JSON(http.StatusOK, convert.ToLabelList(labels, nil, ctx.Org.Organization))
 }
 
 // CreateLabel create a label for a repository
@@ -103,7 +103,8 @@ func CreateLabel(ctx *context.APIContext) {
 		ctx.Error(http.StatusInternalServerError, "NewLabel", err)
 		return
 	}
-	ctx.JSON(http.StatusCreated, convert.ToLabel(label))
+
+	ctx.JSON(http.StatusCreated, convert.ToLabel(label, nil, ctx.Org.Organization))
 }
 
 // GetLabel get label by organization and label id
@@ -148,7 +149,7 @@ func GetLabel(ctx *context.APIContext) {
 		return
 	}
 
-	ctx.JSON(http.StatusOK, convert.ToLabel(label))
+	ctx.JSON(http.StatusOK, convert.ToLabel(label, nil, ctx.Org.Organization))
 }
 
 // EditLabel modify a label for an Organization
@@ -212,7 +213,8 @@ func EditLabel(ctx *context.APIContext) {
 		ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
 		return
 	}
-	ctx.JSON(http.StatusOK, convert.ToLabel(label))
+
+	ctx.JSON(http.StatusOK, convert.ToLabel(label, nil, ctx.Org.Organization))
 }
 
 // DeleteLabel delete a label for an organization
diff --git a/routers/api/v1/repo/issue_label.go b/routers/api/v1/repo/issue_label.go
index d7f64b2d99..0469ae247c 100644
--- a/routers/api/v1/repo/issue_label.go
+++ b/routers/api/v1/repo/issue_label.go
@@ -61,7 +61,7 @@ func ListIssueLabels(ctx *context.APIContext) {
 		return
 	}
 
-	ctx.JSON(http.StatusOK, convert.ToLabelList(issue.Labels))
+	ctx.JSON(http.StatusOK, convert.ToLabelList(issue.Labels, ctx.Repo.Repository, ctx.Repo.Owner))
 }
 
 // AddIssueLabels add labels for an issue
@@ -117,7 +117,7 @@ func AddIssueLabels(ctx *context.APIContext) {
 		return
 	}
 
-	ctx.JSON(http.StatusOK, convert.ToLabelList(labels))
+	ctx.JSON(http.StatusOK, convert.ToLabelList(labels, ctx.Repo.Repository, ctx.Repo.Owner))
 }
 
 // DeleteIssueLabel delete a label for an issue
@@ -243,7 +243,7 @@ func ReplaceIssueLabels(ctx *context.APIContext) {
 		return
 	}
 
-	ctx.JSON(http.StatusOK, convert.ToLabelList(labels))
+	ctx.JSON(http.StatusOK, convert.ToLabelList(labels, ctx.Repo.Repository, ctx.Repo.Owner))
 }
 
 // ClearIssueLabels delete all the labels for an issue
diff --git a/routers/api/v1/repo/label.go b/routers/api/v1/repo/label.go
index 1de5705aa2..67682fc60d 100644
--- a/routers/api/v1/repo/label.go
+++ b/routers/api/v1/repo/label.go
@@ -62,7 +62,7 @@ func ListLabels(ctx *context.APIContext) {
 	}
 
 	ctx.SetTotalCountHeader(count)
-	ctx.JSON(http.StatusOK, convert.ToLabelList(labels))
+	ctx.JSON(http.StatusOK, convert.ToLabelList(labels, ctx.Repo.Repository, nil))
 }
 
 // GetLabel get label by repository and label id
@@ -112,7 +112,7 @@ func GetLabel(ctx *context.APIContext) {
 		return
 	}
 
-	ctx.JSON(http.StatusOK, convert.ToLabel(label))
+	ctx.JSON(http.StatusOK, convert.ToLabel(label, ctx.Repo.Repository, nil))
 }
 
 // CreateLabel create a label for a repository
@@ -165,7 +165,8 @@ func CreateLabel(ctx *context.APIContext) {
 		ctx.Error(http.StatusInternalServerError, "NewLabel", err)
 		return
 	}
-	ctx.JSON(http.StatusCreated, convert.ToLabel(label))
+
+	ctx.JSON(http.StatusCreated, convert.ToLabel(label, ctx.Repo.Repository, nil))
 }
 
 // EditLabel modify a label for a repository
@@ -235,7 +236,8 @@ func EditLabel(ctx *context.APIContext) {
 		ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
 		return
 	}
-	ctx.JSON(http.StatusOK, convert.ToLabel(label))
+
+	ctx.JSON(http.StatusOK, convert.ToLabel(label, ctx.Repo.Repository, nil))
 }
 
 // DeleteLabel delete a label for a repository