From 461f9255541c018ed395c4dcb78baf117f5a65f6 Mon Sep 17 00:00:00 2001
From: Gusted <postmaster@gusted.xyz>
Date: Tue, 19 Mar 2024 20:41:40 +0100
Subject: [PATCH] [BUG] Reflect Cargo index state in settings

- Currently in the Cargo section of the packages setting menu two
buttons are always shown, "Initalize index" and "Rebuild index", however
only of these should be shown depending on the state of the index, if
there's no index the "Initalize index" button should be shown and if
there's an index the "Rebuild index" button should be shown. This patch
does exactly that.
- Resolves #2628
---
 options/locale/locale_en-US.ini              |  1 +
 routers/web/shared/packages/packages.go      | 15 ++++-
 services/packages/cargo/index.go             |  4 +-
 templates/package/shared/cargo.tmpl          | 17 +++---
 tests/integration/api_packages_cargo_test.go | 60 ++++++++++++++++++++
 5 files changed, 87 insertions(+), 10 deletions(-)

diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index b6279d092e..3b3c3e9836 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -3568,6 +3568,7 @@ owner.settings.cargo.rebuild = Rebuild index
 owner.settings.cargo.rebuild.description = Rebuilding can be useful if the index is not synchronized with the stored Cargo packages.
 owner.settings.cargo.rebuild.error = Failed to rebuild Cargo index: %v
 owner.settings.cargo.rebuild.success = The Cargo index was successfully rebuild.
+owner.settings.cargo.rebuild.no_index = Cannot rebuild, no index is initialized.
 owner.settings.cleanuprules.title = Manage cleanup rules
 owner.settings.cleanuprules.add = Add cleanup rule
 owner.settings.cleanuprules.edit = Edit cleanup rule
diff --git a/routers/web/shared/packages/packages.go b/routers/web/shared/packages/packages.go
index 57671ad8f1..af960f1c0c 100644
--- a/routers/web/shared/packages/packages.go
+++ b/routers/web/shared/packages/packages.go
@@ -4,16 +4,19 @@
 package packages
 
 import (
+	"errors"
 	"fmt"
 	"net/http"
 	"time"
 
 	"code.gitea.io/gitea/models/db"
 	packages_model "code.gitea.io/gitea/models/packages"
+	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
+	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/context"
 	"code.gitea.io/gitea/services/forms"
@@ -29,6 +32,12 @@ func SetPackagesContext(ctx *context.Context, owner *user_model.User) {
 	}
 
 	ctx.Data["CleanupRules"] = pcrs
+
+	ctx.Data["CargoIndexExists"], err = repo_model.IsRepositoryModelExist(ctx, owner, cargo_service.IndexRepositoryName)
+	if err != nil {
+		ctx.ServerError("IsRepositoryModelExist", err)
+		return
+	}
 }
 
 func SetRuleAddContext(ctx *context.Context) {
@@ -240,7 +249,11 @@ func RebuildCargoIndex(ctx *context.Context, owner *user_model.User) {
 	err := cargo_service.RebuildIndex(ctx, owner, owner)
 	if err != nil {
 		log.Error("RebuildIndex failed: %v", err)
-		ctx.Flash.Error(ctx.Tr("packages.owner.settings.cargo.rebuild.error", err))
+		if errors.Is(err, util.ErrNotExist) {
+			ctx.Flash.Error(ctx.Tr("packages.owner.settings.cargo.rebuild.no_index"))
+		} else {
+			ctx.Flash.Error(ctx.Tr("packages.owner.settings.cargo.rebuild.error", err))
+		}
 	} else {
 		ctx.Flash.Success(ctx.Tr("packages.owner.settings.cargo.rebuild.success"))
 	}
diff --git a/services/packages/cargo/index.go b/services/packages/cargo/index.go
index e8a8313625..59823cd3de 100644
--- a/services/packages/cargo/index.go
+++ b/services/packages/cargo/index.go
@@ -62,9 +62,9 @@ func InitializeIndexRepository(ctx context.Context, doer, owner *user_model.User
 }
 
 func RebuildIndex(ctx context.Context, doer, owner *user_model.User) error {
-	repo, err := getOrCreateIndexRepository(ctx, doer, owner)
+	repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner.Name, IndexRepositoryName)
 	if err != nil {
-		return err
+		return fmt.Errorf("GetRepositoryByOwnerAndName: %w", err)
 	}
 
 	ps, err := packages_model.GetPackagesByType(ctx, owner.ID, packages_model.TypeCargo)
diff --git a/templates/package/shared/cargo.tmpl b/templates/package/shared/cargo.tmpl
index 401d909002..5b0f63965d 100644
--- a/templates/package/shared/cargo.tmpl
+++ b/templates/package/shared/cargo.tmpl
@@ -3,13 +3,7 @@
 </h4>
 <div class="ui attached segment">
 	<div class="ui form">
-		<div class="field">
-			<label>{{ctx.Locale.Tr "packages.owner.settings.cargo.initialize.description"}}</label>
-		</div>
-		<form class="field" action="{{.Link}}/cargo/initialize" method="post">
-			{{.CsrfTokenHtml}}
-			<button class="ui primary button">{{ctx.Locale.Tr "packages.owner.settings.cargo.initialize"}}</button>
-		</form>
+		{{if .CargoIndexExists}}
 		<div class="field">
 			<label>{{ctx.Locale.Tr "packages.owner.settings.cargo.rebuild.description"}}</label>
 		</div>
@@ -17,6 +11,15 @@
 			{{.CsrfTokenHtml}}
 			<button class="ui primary button">{{ctx.Locale.Tr "packages.owner.settings.cargo.rebuild"}}</button>
 		</form>
+		{{else}}
+		<div class="field">
+			<label>{{ctx.Locale.Tr "packages.owner.settings.cargo.initialize.description"}}</label>
+		</div>
+		<form class="field" action="{{.Link}}/cargo/initialize" method="post">
+			{{.CsrfTokenHtml}}
+			<button class="ui primary button">{{ctx.Locale.Tr "packages.owner.settings.cargo.initialize"}}</button>
+		</form>
+		{{end}}
 		<div class="field">
 			<label>{{ctx.Locale.Tr "packages.registry.documentation" "Cargo" "https://forgejo.org/docs/latest/user/packages/cargo/"}}</label>
 		</div>
diff --git a/tests/integration/api_packages_cargo_test.go b/tests/integration/api_packages_cargo_test.go
index c0705e0de5..869d90066a 100644
--- a/tests/integration/api_packages_cargo_test.go
+++ b/tests/integration/api_packages_cargo_test.go
@@ -23,6 +23,7 @@ import (
 	cargo_module "code.gitea.io/gitea/modules/packages/cargo"
 	"code.gitea.io/gitea/modules/setting"
 	cargo_router "code.gitea.io/gitea/routers/api/packages/cargo"
+	gitea_context "code.gitea.io/gitea/services/context"
 	cargo_service "code.gitea.io/gitea/services/packages/cargo"
 	"code.gitea.io/gitea/tests"
 
@@ -385,3 +386,62 @@ func testPackageCargo(t *testing.T, _ *neturl.URL) {
 		assert.Equal(t, user.DisplayName(), owners.Users[0].Name)
 	})
 }
+
+func TestRebuildCargo(t *testing.T) {
+	onGiteaRun(t, func(t *testing.T, u *neturl.URL) {
+		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+		session := loginUser(t, user.Name)
+		unittest.AssertExistsIf(t, false, &repo_model.Repository{OwnerID: user.ID, Name: cargo_service.IndexRepositoryName})
+
+		t.Run("No index", func(t *testing.T) {
+			defer tests.PrintCurrentTest(t)()
+
+			req := NewRequestWithValues(t, "POST", "/user/settings/packages/cargo/rebuild", map[string]string{
+				"_csrf": GetCSRF(t, session, "/user/settings/packages"),
+			})
+			session.MakeRequest(t, req, http.StatusSeeOther)
+
+			flashCookie := session.GetCookie(gitea_context.CookieNameFlash)
+			assert.NotNil(t, flashCookie)
+			assert.EqualValues(t, "error%3DCannot%2Brebuild%252C%2Bno%2Bindex%2Bis%2Binitialized.", flashCookie.Value)
+			unittest.AssertExistsIf(t, false, &repo_model.Repository{OwnerID: user.ID, Name: cargo_service.IndexRepositoryName})
+		})
+
+		t.Run("Initialize Cargo", func(t *testing.T) {
+			defer tests.PrintCurrentTest(t)()
+
+			req := NewRequest(t, "GET", "/user/settings/packages")
+			resp := session.MakeRequest(t, req, http.StatusOK)
+			htmlDoc := NewHTMLParser(t, resp.Body)
+
+			htmlDoc.AssertElement(t, `form[action="/user/settings/packages/cargo/rebuild"]`, false)
+			htmlDoc.AssertElement(t, `form[action="/user/settings/packages/cargo/initialize"]`, true)
+
+			req = NewRequestWithValues(t, "POST", "/user/settings/packages/cargo/initialize", map[string]string{
+				"_csrf": htmlDoc.GetCSRF(),
+			})
+			session.MakeRequest(t, req, http.StatusSeeOther)
+			unittest.AssertExistsIf(t, true, &repo_model.Repository{OwnerID: user.ID, Name: cargo_service.IndexRepositoryName})
+
+			req = NewRequest(t, "GET", "/user/settings/packages")
+			resp = session.MakeRequest(t, req, http.StatusOK)
+			htmlDoc = NewHTMLParser(t, resp.Body)
+
+			htmlDoc.AssertElement(t, `form[action="/user/settings/packages/cargo/rebuild"]`, true)
+			htmlDoc.AssertElement(t, `form[action="/user/settings/packages/cargo/initialize"]`, false)
+		})
+
+		t.Run("With index", func(t *testing.T) {
+			defer tests.PrintCurrentTest(t)()
+
+			req := NewRequestWithValues(t, "POST", "/user/settings/packages/cargo/rebuild", map[string]string{
+				"_csrf": GetCSRF(t, session, "/user/settings/packages"),
+			})
+			session.MakeRequest(t, req, http.StatusSeeOther)
+
+			flashCookie := session.GetCookie(gitea_context.CookieNameFlash)
+			assert.NotNil(t, flashCookie)
+			assert.EqualValues(t, "success%3DThe%2BCargo%2Bindex%2Bwas%2Bsuccessfully%2Brebuild.", flashCookie.Value)
+		})
+	})
+}