summaryrefslogtreecommitdiffstats
path: root/routers/web/shared
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
committerDaniel Baumann <daniel@debian.org>2024-12-12 23:57:56 +0100
commite68b9d00a6e05b3a941f63ffb696f91e554ac5ec (patch)
tree97775d6c13b0f416af55314eb6a89ef792474615 /routers/web/shared
parentInitial commit. (diff)
downloadforgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.tar.xz
forgejo-e68b9d00a6e05b3a941f63ffb696f91e554ac5ec.zip
Adding upstream version 9.0.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to '')
-rw-r--r--routers/web/shared/actions/runners.go161
-rw-r--r--routers/web/shared/actions/variables.go65
-rw-r--r--routers/web/shared/packages/packages.go260
-rw-r--r--routers/web/shared/project/column.go48
-rw-r--r--routers/web/shared/secrets/secrets.go53
-rw-r--r--routers/web/shared/user/header.go163
6 files changed, 750 insertions, 0 deletions
diff --git a/routers/web/shared/actions/runners.go b/routers/web/shared/actions/runners.go
new file mode 100644
index 0000000..f389332
--- /dev/null
+++ b/routers/web/shared/actions/runners.go
@@ -0,0 +1,161 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package actions
+
+import (
+ "errors"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/forms"
+)
+
+// RunnersList prepares data for runners list
+func RunnersList(ctx *context.Context, opts actions_model.FindRunnerOptions) {
+ runners, count, err := db.FindAndCount[actions_model.ActionRunner](ctx, opts)
+ if err != nil {
+ ctx.ServerError("CountRunners", err)
+ return
+ }
+
+ if err := actions_model.RunnerList(runners).LoadAttributes(ctx); err != nil {
+ ctx.ServerError("LoadAttributes", err)
+ return
+ }
+
+ // ownid=0,repo_id=0,means this token is used for global
+ var token *actions_model.ActionRunnerToken
+ token, err = actions_model.GetLatestRunnerToken(ctx, opts.OwnerID, opts.RepoID)
+ if errors.Is(err, util.ErrNotExist) || (token != nil && !token.IsActive) {
+ token, err = actions_model.NewRunnerToken(ctx, opts.OwnerID, opts.RepoID)
+ if err != nil {
+ ctx.ServerError("CreateRunnerToken", err)
+ return
+ }
+ } else if err != nil {
+ ctx.ServerError("GetLatestRunnerToken", err)
+ return
+ }
+
+ ctx.Data["Keyword"] = opts.Filter
+ ctx.Data["Runners"] = runners
+ ctx.Data["Total"] = count
+ ctx.Data["RegistrationToken"] = token.Token
+ ctx.Data["RunnerOwnerID"] = opts.OwnerID
+ ctx.Data["RunnerRepoID"] = opts.RepoID
+ ctx.Data["SortType"] = opts.Sort
+
+ pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
+
+ ctx.Data["Page"] = pager
+}
+
+// RunnerDetails prepares data for runners edit page
+func RunnerDetails(ctx *context.Context, page int, runnerID, ownerID, repoID int64) {
+ runner, err := actions_model.GetRunnerByID(ctx, runnerID)
+ if err != nil {
+ ctx.ServerError("GetRunnerByID", err)
+ return
+ }
+ if err := runner.LoadAttributes(ctx); err != nil {
+ ctx.ServerError("LoadAttributes", err)
+ return
+ }
+ if !runner.Editable(ownerID, repoID) {
+ err = errors.New("no permission to edit this runner")
+ ctx.NotFound("RunnerDetails", err)
+ return
+ }
+
+ ctx.Data["Runner"] = runner
+
+ opts := actions_model.FindTaskOptions{
+ ListOptions: db.ListOptions{
+ Page: page,
+ PageSize: 30,
+ },
+ Status: actions_model.StatusUnknown, // Unknown means all
+ RunnerID: runner.ID,
+ }
+
+ tasks, count, err := db.FindAndCount[actions_model.ActionTask](ctx, opts)
+ if err != nil {
+ ctx.ServerError("CountTasks", err)
+ return
+ }
+
+ if err = actions_model.TaskList(tasks).LoadAttributes(ctx); err != nil {
+ ctx.ServerError("TasksLoadAttributes", err)
+ return
+ }
+
+ ctx.Data["Tasks"] = tasks
+ pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
+ ctx.Data["Page"] = pager
+}
+
+// RunnerDetailsEditPost response for edit runner details
+func RunnerDetailsEditPost(ctx *context.Context, runnerID, ownerID, repoID int64, redirectTo string) {
+ runner, err := actions_model.GetRunnerByID(ctx, runnerID)
+ if err != nil {
+ log.Warn("RunnerDetailsEditPost.GetRunnerByID failed: %v, url: %s", err, ctx.Req.URL)
+ ctx.ServerError("RunnerDetailsEditPost.GetRunnerByID", err)
+ return
+ }
+ if !runner.Editable(ownerID, repoID) {
+ ctx.NotFound("RunnerDetailsEditPost.Editable", util.NewPermissionDeniedErrorf("no permission to edit this runner"))
+ return
+ }
+
+ form := web.GetForm(ctx).(*forms.EditRunnerForm)
+ runner.Description = form.Description
+
+ err = actions_model.UpdateRunner(ctx, runner, "description")
+ if err != nil {
+ log.Warn("RunnerDetailsEditPost.UpdateRunner failed: %v, url: %s", err, ctx.Req.URL)
+ ctx.Flash.Warning(ctx.Tr("actions.runners.update_runner_failed"))
+ ctx.Redirect(redirectTo)
+ return
+ }
+
+ log.Debug("RunnerDetailsEditPost success: %s", ctx.Req.URL)
+
+ ctx.Flash.Success(ctx.Tr("actions.runners.update_runner_success"))
+ ctx.Redirect(redirectTo)
+}
+
+// RunnerResetRegistrationToken reset registration token
+func RunnerResetRegistrationToken(ctx *context.Context, ownerID, repoID int64, redirectTo string) {
+ _, err := actions_model.NewRunnerToken(ctx, ownerID, repoID)
+ if err != nil {
+ ctx.ServerError("ResetRunnerRegistrationToken", err)
+ return
+ }
+
+ ctx.Flash.Success(ctx.Tr("actions.runners.reset_registration_token_success"))
+ ctx.Redirect(redirectTo)
+}
+
+// RunnerDeletePost response for deleting a runner
+func RunnerDeletePost(ctx *context.Context, runnerID int64,
+ successRedirectTo, failedRedirectTo string,
+) {
+ if err := actions_model.DeleteRunner(ctx, runnerID); err != nil {
+ log.Warn("DeleteRunnerPost.UpdateRunner failed: %v, url: %s", err, ctx.Req.URL)
+ ctx.Flash.Warning(ctx.Tr("actions.runners.delete_runner_failed"))
+
+ ctx.JSONRedirect(failedRedirectTo)
+ return
+ }
+
+ log.Info("DeleteRunnerPost success: %s", ctx.Req.URL)
+
+ ctx.Flash.Success(ctx.Tr("actions.runners.delete_runner_success"))
+
+ ctx.JSONRedirect(successRedirectTo)
+}
diff --git a/routers/web/shared/actions/variables.go b/routers/web/shared/actions/variables.go
new file mode 100644
index 0000000..79c03e4
--- /dev/null
+++ b/routers/web/shared/actions/variables.go
@@ -0,0 +1,65 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package actions
+
+import (
+ actions_model "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/web"
+ actions_service "code.gitea.io/gitea/services/actions"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/forms"
+)
+
+func SetVariablesContext(ctx *context.Context, ownerID, repoID int64) {
+ variables, err := db.Find[actions_model.ActionVariable](ctx, actions_model.FindVariablesOpts{
+ OwnerID: ownerID,
+ RepoID: repoID,
+ })
+ if err != nil {
+ ctx.ServerError("FindVariables", err)
+ return
+ }
+ ctx.Data["Variables"] = variables
+}
+
+func CreateVariable(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
+ form := web.GetForm(ctx).(*forms.EditVariableForm)
+
+ v, err := actions_service.CreateVariable(ctx, ownerID, repoID, form.Name, form.Data)
+ if err != nil {
+ log.Error("CreateVariable: %v", err)
+ ctx.JSONError(ctx.Tr("actions.variables.creation.failed"))
+ return
+ }
+
+ ctx.Flash.Success(ctx.Tr("actions.variables.creation.success", v.Name))
+ ctx.JSONRedirect(redirectURL)
+}
+
+func UpdateVariable(ctx *context.Context, redirectURL string) {
+ id := ctx.ParamsInt64(":variable_id")
+ form := web.GetForm(ctx).(*forms.EditVariableForm)
+
+ if ok, err := actions_service.UpdateVariable(ctx, id, form.Name, form.Data); err != nil || !ok {
+ log.Error("UpdateVariable: %v", err)
+ ctx.JSONError(ctx.Tr("actions.variables.update.failed"))
+ return
+ }
+ ctx.Flash.Success(ctx.Tr("actions.variables.update.success"))
+ ctx.JSONRedirect(redirectURL)
+}
+
+func DeleteVariable(ctx *context.Context, redirectURL string) {
+ id := ctx.ParamsInt64(":variable_id")
+
+ if err := actions_service.DeleteVariableByID(ctx, id); err != nil {
+ log.Error("Delete variable [%d] failed: %v", id, err)
+ ctx.JSONError(ctx.Tr("actions.variables.deletion.failed"))
+ return
+ }
+ ctx.Flash.Success(ctx.Tr("actions.variables.deletion.success"))
+ ctx.JSONRedirect(redirectURL)
+}
diff --git a/routers/web/shared/packages/packages.go b/routers/web/shared/packages/packages.go
new file mode 100644
index 0000000..af960f1
--- /dev/null
+++ b/routers/web/shared/packages/packages.go
@@ -0,0 +1,260 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+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"
+ cargo_service "code.gitea.io/gitea/services/packages/cargo"
+ container_service "code.gitea.io/gitea/services/packages/container"
+)
+
+func SetPackagesContext(ctx *context.Context, owner *user_model.User) {
+ pcrs, err := packages_model.GetCleanupRulesByOwner(ctx, owner.ID)
+ if err != nil {
+ ctx.ServerError("GetCleanupRulesByOwner", err)
+ return
+ }
+
+ 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) {
+ setRuleEditContext(ctx, nil)
+}
+
+func SetRuleEditContext(ctx *context.Context, owner *user_model.User) {
+ pcr := getCleanupRuleByContext(ctx, owner)
+ if pcr == nil {
+ return
+ }
+
+ setRuleEditContext(ctx, pcr)
+}
+
+func setRuleEditContext(ctx *context.Context, pcr *packages_model.PackageCleanupRule) {
+ ctx.Data["IsEditRule"] = pcr != nil
+
+ if pcr == nil {
+ pcr = &packages_model.PackageCleanupRule{}
+ }
+ ctx.Data["CleanupRule"] = pcr
+ ctx.Data["AvailableTypes"] = packages_model.TypeList
+}
+
+func PerformRuleAddPost(ctx *context.Context, owner *user_model.User, redirectURL string, template base.TplName) {
+ performRuleEditPost(ctx, owner, nil, redirectURL, template)
+}
+
+func PerformRuleEditPost(ctx *context.Context, owner *user_model.User, redirectURL string, template base.TplName) {
+ pcr := getCleanupRuleByContext(ctx, owner)
+ if pcr == nil {
+ return
+ }
+
+ form := web.GetForm(ctx).(*forms.PackageCleanupRuleForm)
+
+ if form.Action == "remove" {
+ if err := packages_model.DeleteCleanupRuleByID(ctx, pcr.ID); err != nil {
+ ctx.ServerError("DeleteCleanupRuleByID", err)
+ return
+ }
+
+ ctx.Flash.Success(ctx.Tr("packages.owner.settings.cleanuprules.success.delete"))
+ ctx.Redirect(redirectURL)
+ } else {
+ performRuleEditPost(ctx, owner, pcr, redirectURL, template)
+ }
+}
+
+func performRuleEditPost(ctx *context.Context, owner *user_model.User, pcr *packages_model.PackageCleanupRule, redirectURL string, template base.TplName) {
+ isEditRule := pcr != nil
+
+ if pcr == nil {
+ pcr = &packages_model.PackageCleanupRule{}
+ }
+
+ form := web.GetForm(ctx).(*forms.PackageCleanupRuleForm)
+
+ pcr.Enabled = form.Enabled
+ pcr.OwnerID = owner.ID
+ pcr.KeepCount = form.KeepCount
+ pcr.KeepPattern = form.KeepPattern
+ pcr.RemoveDays = form.RemoveDays
+ pcr.RemovePattern = form.RemovePattern
+ pcr.MatchFullName = form.MatchFullName
+
+ ctx.Data["IsEditRule"] = isEditRule
+ ctx.Data["CleanupRule"] = pcr
+ ctx.Data["AvailableTypes"] = packages_model.TypeList
+
+ if ctx.HasError() {
+ ctx.HTML(http.StatusOK, template)
+ return
+ }
+
+ if isEditRule {
+ if err := packages_model.UpdateCleanupRule(ctx, pcr); err != nil {
+ ctx.ServerError("UpdateCleanupRule", err)
+ return
+ }
+ } else {
+ pcr.Type = packages_model.Type(form.Type)
+
+ if has, err := packages_model.HasOwnerCleanupRuleForPackageType(ctx, owner.ID, pcr.Type); err != nil {
+ ctx.ServerError("HasOwnerCleanupRuleForPackageType", err)
+ return
+ } else if has {
+ ctx.Data["Err_Type"] = true
+ ctx.HTML(http.StatusOK, template)
+ return
+ }
+
+ var err error
+ if pcr, err = packages_model.InsertCleanupRule(ctx, pcr); err != nil {
+ ctx.ServerError("InsertCleanupRule", err)
+ return
+ }
+ }
+
+ ctx.Flash.Success(ctx.Tr("packages.owner.settings.cleanuprules.success.update"))
+ ctx.Redirect(fmt.Sprintf("%s/rules/%d", redirectURL, pcr.ID))
+}
+
+func SetRulePreviewContext(ctx *context.Context, owner *user_model.User) {
+ pcr := getCleanupRuleByContext(ctx, owner)
+ if pcr == nil {
+ return
+ }
+
+ if err := pcr.CompiledPattern(); err != nil {
+ ctx.ServerError("CompiledPattern", err)
+ return
+ }
+
+ olderThan := time.Now().AddDate(0, 0, -pcr.RemoveDays)
+
+ packages, err := packages_model.GetPackagesByType(ctx, pcr.OwnerID, pcr.Type)
+ if err != nil {
+ ctx.ServerError("GetPackagesByType", err)
+ return
+ }
+
+ versionsToRemove := make([]*packages_model.PackageDescriptor, 0, 10)
+
+ for _, p := range packages {
+ pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
+ PackageID: p.ID,
+ IsInternal: optional.Some(false),
+ Sort: packages_model.SortCreatedDesc,
+ Paginator: db.NewAbsoluteListOptions(pcr.KeepCount, 200),
+ })
+ if err != nil {
+ ctx.ServerError("SearchVersions", err)
+ return
+ }
+ for _, pv := range pvs {
+ if skip, err := container_service.ShouldBeSkipped(ctx, pcr, p, pv); err != nil {
+ ctx.ServerError("ShouldBeSkipped", err)
+ return
+ } else if skip {
+ continue
+ }
+
+ toMatch := pv.LowerVersion
+ if pcr.MatchFullName {
+ toMatch = p.LowerName + "/" + pv.LowerVersion
+ }
+
+ if pcr.KeepPatternMatcher != nil && pcr.KeepPatternMatcher.MatchString(toMatch) {
+ continue
+ }
+ if pv.CreatedUnix.AsLocalTime().After(olderThan) {
+ continue
+ }
+ if pcr.RemovePatternMatcher != nil && !pcr.RemovePatternMatcher.MatchString(toMatch) {
+ continue
+ }
+
+ pd, err := packages_model.GetPackageDescriptor(ctx, pv)
+ if err != nil {
+ ctx.ServerError("GetPackageDescriptor", err)
+ return
+ }
+ versionsToRemove = append(versionsToRemove, pd)
+ }
+ }
+
+ ctx.Data["CleanupRule"] = pcr
+ ctx.Data["VersionsToRemove"] = versionsToRemove
+}
+
+func getCleanupRuleByContext(ctx *context.Context, owner *user_model.User) *packages_model.PackageCleanupRule {
+ id := ctx.FormInt64("id")
+ if id == 0 {
+ id = ctx.ParamsInt64("id")
+ }
+
+ pcr, err := packages_model.GetCleanupRuleByID(ctx, id)
+ if err != nil {
+ if err == packages_model.ErrPackageCleanupRuleNotExist {
+ ctx.NotFound("", err)
+ } else {
+ ctx.ServerError("GetCleanupRuleByID", err)
+ }
+ return nil
+ }
+
+ if pcr != nil && pcr.OwnerID == owner.ID {
+ return pcr
+ }
+
+ ctx.NotFound("", fmt.Errorf("PackageCleanupRule[%v] not associated to owner %v", id, owner))
+
+ return nil
+}
+
+func InitializeCargoIndex(ctx *context.Context, owner *user_model.User) {
+ err := cargo_service.InitializeIndexRepository(ctx, owner, owner)
+ if err != nil {
+ log.Error("InitializeIndexRepository failed: %v", err)
+ ctx.Flash.Error(ctx.Tr("packages.owner.settings.cargo.initialize.error", err))
+ } else {
+ ctx.Flash.Success(ctx.Tr("packages.owner.settings.cargo.initialize.success"))
+ }
+}
+
+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)
+ 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/routers/web/shared/project/column.go b/routers/web/shared/project/column.go
new file mode 100644
index 0000000..599842e
--- /dev/null
+++ b/routers/web/shared/project/column.go
@@ -0,0 +1,48 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package project
+
+import (
+ project_model "code.gitea.io/gitea/models/project"
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/services/context"
+)
+
+// MoveColumns moves or keeps columns in a project and sorts them inside that project
+func MoveColumns(ctx *context.Context) {
+ project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
+ if err != nil {
+ ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
+ return
+ }
+ if !project.CanBeAccessedByOwnerRepo(ctx.ContextUser.ID, ctx.Repo.Repository) {
+ ctx.NotFound("CanBeAccessedByOwnerRepo", nil)
+ return
+ }
+
+ type movedColumnsForm struct {
+ Columns []struct {
+ ColumnID int64 `json:"columnID"`
+ Sorting int64 `json:"sorting"`
+ } `json:"columns"`
+ }
+
+ form := &movedColumnsForm{}
+ if err = json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
+ ctx.ServerError("DecodeMovedColumnsForm", err)
+ return
+ }
+
+ sortedColumnIDs := make(map[int64]int64)
+ for _, column := range form.Columns {
+ sortedColumnIDs[column.Sorting] = column.ColumnID
+ }
+
+ if err = project_model.MoveColumnsOnProject(ctx, project, sortedColumnIDs); err != nil {
+ ctx.ServerError("MoveColumnsOnProject", err)
+ return
+ }
+
+ ctx.JSONOK()
+}
diff --git a/routers/web/shared/secrets/secrets.go b/routers/web/shared/secrets/secrets.go
new file mode 100644
index 0000000..3bd421f
--- /dev/null
+++ b/routers/web/shared/secrets/secrets.go
@@ -0,0 +1,53 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package secrets
+
+import (
+ "code.gitea.io/gitea/models/db"
+ secret_model "code.gitea.io/gitea/models/secret"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/forms"
+ secret_service "code.gitea.io/gitea/services/secrets"
+)
+
+func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) {
+ secrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{OwnerID: ownerID, RepoID: repoID})
+ if err != nil {
+ ctx.ServerError("FindSecrets", err)
+ return
+ }
+
+ ctx.Data["Secrets"] = secrets
+}
+
+func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
+ form := web.GetForm(ctx).(*forms.AddSecretForm)
+
+ s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, util.ReserveLineBreakForTextarea(form.Data))
+ if err != nil {
+ log.Error("CreateOrUpdateSecret failed: %v", err)
+ ctx.JSONError(ctx.Tr("secrets.creation.failed"))
+ return
+ }
+
+ ctx.Flash.Success(ctx.Tr("secrets.creation.success", s.Name))
+ ctx.JSONRedirect(redirectURL)
+}
+
+func PerformSecretsDelete(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
+ id := ctx.FormInt64("id")
+
+ err := secret_service.DeleteSecretByID(ctx, ownerID, repoID, id)
+ if err != nil {
+ log.Error("DeleteSecretByID(%d) failed: %v", id, err)
+ ctx.JSONError(ctx.Tr("secrets.deletion.failed"))
+ return
+ }
+
+ ctx.Flash.Success(ctx.Tr("secrets.deletion.success"))
+ ctx.JSONRedirect(redirectURL)
+}
diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go
new file mode 100644
index 0000000..fd7605c
--- /dev/null
+++ b/routers/web/shared/user/header.go
@@ -0,0 +1,163 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package user
+
+import (
+ "net/url"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/organization"
+ packages_model "code.gitea.io/gitea/models/packages"
+ access_model "code.gitea.io/gitea/models/perm/access"
+ project_model "code.gitea.io/gitea/models/project"
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unit"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/markup/markdown"
+ "code.gitea.io/gitea/modules/optional"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/services/context"
+)
+
+// prepareContextForCommonProfile store some common data into context data for user's profile related pages (including the nav menu)
+// It is designed to be fast and safe to be called multiple times in one request
+func prepareContextForCommonProfile(ctx *context.Context) {
+ ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled
+ ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
+ ctx.Data["EnableFeed"] = setting.Other.EnableFeed
+ ctx.Data["FeedURL"] = ctx.ContextUser.HomeLink()
+}
+
+// PrepareContextForProfileBigAvatar set the context for big avatar view on the profile page
+func PrepareContextForProfileBigAvatar(ctx *context.Context) {
+ prepareContextForCommonProfile(ctx)
+
+ ctx.Data["IsBlocked"] = ctx.Doer != nil && user_model.IsBlocked(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
+ ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID)
+ ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate
+ if setting.Service.UserLocationMapURL != "" {
+ ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location)
+ }
+ // Show OpenID URIs
+ openIDs, err := user_model.GetUserOpenIDs(ctx, ctx.ContextUser.ID)
+ if err != nil {
+ ctx.ServerError("GetUserOpenIDs", err)
+ return
+ }
+ ctx.Data["OpenIDs"] = openIDs
+ if len(ctx.ContextUser.Description) != 0 {
+ content, err := markdown.RenderString(&markup.RenderContext{
+ Metas: map[string]string{"mode": "document"},
+ Ctx: ctx,
+ }, ctx.ContextUser.Description)
+ if err != nil {
+ ctx.ServerError("RenderString", err)
+ return
+ }
+ ctx.Data["RenderedDescription"] = content
+ }
+
+ showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
+ orgs, err := db.Find[organization.Organization](ctx, organization.FindOrgOptions{
+ UserID: ctx.ContextUser.ID,
+ IncludePrivate: showPrivate,
+ })
+ if err != nil {
+ ctx.ServerError("FindOrgs", err)
+ return
+ }
+ ctx.Data["Orgs"] = orgs
+ ctx.Data["HasOrgsVisible"] = organization.HasOrgsVisible(ctx, orgs, ctx.Doer)
+
+ badges, _, err := user_model.GetUserBadges(ctx, ctx.ContextUser)
+ if err != nil {
+ ctx.ServerError("GetUserBadges", err)
+ return
+ }
+ ctx.Data["Badges"] = badges
+
+ // in case the numbers are already provided by other functions, no need to query again (which is slow)
+ if _, ok := ctx.Data["NumFollowers"]; !ok {
+ _, ctx.Data["NumFollowers"], _ = user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{PageSize: 1, Page: 1})
+ }
+ if _, ok := ctx.Data["NumFollowing"]; !ok {
+ _, ctx.Data["NumFollowing"], _ = user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{PageSize: 1, Page: 1})
+ }
+}
+
+func FindUserProfileReadme(ctx *context.Context, doer *user_model.User) (profileDbRepo *repo_model.Repository, profileGitRepo *git.Repository, profileReadmeBlob *git.Blob, profileClose func()) {
+ profileDbRepo, err := repo_model.GetRepositoryByName(ctx, ctx.ContextUser.ID, ".profile")
+ if err == nil {
+ perm, err := access_model.GetUserRepoPermission(ctx, profileDbRepo, doer)
+ if err == nil && !profileDbRepo.IsEmpty && perm.CanRead(unit.TypeCode) {
+ if profileGitRepo, err = gitrepo.OpenRepository(ctx, profileDbRepo); err != nil {
+ log.Error("FindUserProfileReadme failed to OpenRepository: %v", err)
+ } else {
+ if commit, err := profileGitRepo.GetBranchCommit(profileDbRepo.DefaultBranch); err != nil {
+ log.Error("FindUserProfileReadme failed to GetBranchCommit: %v", err)
+ } else {
+ profileReadmeBlob, _ = commit.GetBlobByFoldedPath("README.md")
+ }
+ }
+ }
+ } else if !repo_model.IsErrRepoNotExist(err) {
+ log.Error("FindUserProfileReadme failed to GetRepositoryByName: %v", err)
+ }
+ return profileDbRepo, profileGitRepo, profileReadmeBlob, func() {
+ if profileGitRepo != nil {
+ _ = profileGitRepo.Close()
+ }
+ }
+}
+
+func RenderUserHeader(ctx *context.Context) {
+ prepareContextForCommonProfile(ctx)
+
+ _, _, profileReadmeBlob, profileClose := FindUserProfileReadme(ctx, ctx.Doer)
+ defer profileClose()
+ ctx.Data["HasProfileReadme"] = profileReadmeBlob != nil
+}
+
+func LoadHeaderCount(ctx *context.Context) error {
+ prepareContextForCommonProfile(ctx)
+
+ var err error
+
+ ctx.Data["RepoCount"], err = repo_model.CountRepository(ctx, &repo_model.SearchRepoOptions{
+ Actor: ctx.Doer,
+ OwnerID: ctx.ContextUser.ID,
+ Private: ctx.IsSigned,
+ Collaborate: optional.Some(false),
+ IncludeDescription: setting.UI.SearchRepoDescription,
+ })
+ if err != nil {
+ return err
+ }
+
+ var projectType project_model.Type
+ if ctx.ContextUser.IsOrganization() {
+ projectType = project_model.TypeOrganization
+ } else {
+ projectType = project_model.TypeIndividual
+ }
+ ctx.Data["ProjectCount"], err = db.Count[project_model.Project](ctx, project_model.SearchOptions{
+ OwnerID: ctx.ContextUser.ID,
+ IsClosed: optional.Some(false),
+ Type: projectType,
+ })
+ if err != nil {
+ return err
+ }
+ ctx.Data["PackageCount"], err = packages_model.CountOwnerPackages(ctx, ctx.ContextUser.ID)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}