aboutsummaryrefslogtreecommitdiffstats
path: root/routers/common
diff options
context:
space:
mode:
Diffstat (limited to 'routers/common')
-rw-r--r--routers/common/actions.go71
-rw-r--r--routers/common/blockexpensive.go90
-rw-r--r--routers/common/blockexpensive_test.go30
-rw-r--r--routers/common/db.go4
-rw-r--r--routers/common/markup.go2
-rw-r--r--routers/common/pagetmpl.go75
6 files changed, 269 insertions, 3 deletions
diff --git a/routers/common/actions.go b/routers/common/actions.go
new file mode 100644
index 0000000000..a4eabb6ba2
--- /dev/null
+++ b/routers/common/actions.go
@@ -0,0 +1,71 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package common
+
+import (
+ "fmt"
+ "strings"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/modules/actions"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
+)
+
+func DownloadActionsRunJobLogsWithIndex(ctx *context.Base, ctxRepo *repo_model.Repository, runID, jobIndex int64) error {
+ runJobs, err := actions_model.GetRunJobsByRunID(ctx, runID)
+ if err != nil {
+ return fmt.Errorf("GetRunJobsByRunID: %w", err)
+ }
+ if err = runJobs.LoadRepos(ctx); err != nil {
+ return fmt.Errorf("LoadRepos: %w", err)
+ }
+ if jobIndex < 0 || jobIndex >= int64(len(runJobs)) {
+ return util.NewNotExistErrorf("job index is out of range: %d", jobIndex)
+ }
+ return DownloadActionsRunJobLogs(ctx, ctxRepo, runJobs[jobIndex])
+}
+
+func DownloadActionsRunJobLogs(ctx *context.Base, ctxRepo *repo_model.Repository, curJob *actions_model.ActionRunJob) error {
+ if curJob.Repo.ID != ctxRepo.ID {
+ return util.NewNotExistErrorf("job not found")
+ }
+
+ if curJob.TaskID == 0 {
+ return util.NewNotExistErrorf("job not started")
+ }
+
+ if err := curJob.LoadRun(ctx); err != nil {
+ return fmt.Errorf("LoadRun: %w", err)
+ }
+
+ task, err := actions_model.GetTaskByID(ctx, curJob.TaskID)
+ if err != nil {
+ return fmt.Errorf("GetTaskByID: %w", err)
+ }
+
+ if task.LogExpired {
+ return util.NewNotExistErrorf("logs have been cleaned up")
+ }
+
+ reader, err := actions.OpenLogs(ctx, task.LogInStorage, task.LogFilename)
+ if err != nil {
+ return fmt.Errorf("OpenLogs: %w", err)
+ }
+ defer reader.Close()
+
+ workflowName := curJob.Run.WorkflowID
+ if p := strings.Index(workflowName, "."); p > 0 {
+ workflowName = workflowName[0:p]
+ }
+ ctx.ServeContent(reader, &context.ServeHeaderOptions{
+ Filename: fmt.Sprintf("%v-%v-%v.log", workflowName, curJob.Name, task.ID),
+ ContentLength: &task.LogSize,
+ ContentType: "text/plain",
+ ContentTypeCharset: "utf-8",
+ Disposition: "attachment",
+ })
+ return nil
+}
diff --git a/routers/common/blockexpensive.go b/routers/common/blockexpensive.go
new file mode 100644
index 0000000000..f52aa2b709
--- /dev/null
+++ b/routers/common/blockexpensive.go
@@ -0,0 +1,90 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package common
+
+import (
+ "net/http"
+ "strings"
+
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/reqctx"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/web/middleware"
+
+ "github.com/go-chi/chi/v5"
+)
+
+func BlockExpensive() func(next http.Handler) http.Handler {
+ if !setting.Service.BlockAnonymousAccessExpensive {
+ return nil
+ }
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ ret := determineRequestPriority(reqctx.FromContext(req.Context()))
+ if !ret.SignedIn {
+ if ret.Expensive || ret.LongPolling {
+ http.Redirect(w, req, setting.AppSubURL+"/user/login", http.StatusSeeOther)
+ return
+ }
+ }
+ next.ServeHTTP(w, req)
+ })
+ }
+}
+
+func isRoutePathExpensive(routePattern string) bool {
+ if strings.HasPrefix(routePattern, "/user/") || strings.HasPrefix(routePattern, "/login/") {
+ return false
+ }
+
+ expensivePaths := []string{
+ // code related
+ "/{username}/{reponame}/archive/",
+ "/{username}/{reponame}/blame/",
+ "/{username}/{reponame}/commit/",
+ "/{username}/{reponame}/commits/",
+ "/{username}/{reponame}/graph",
+ "/{username}/{reponame}/media/",
+ "/{username}/{reponame}/raw/",
+ "/{username}/{reponame}/src/",
+
+ // issue & PR related (no trailing slash)
+ "/{username}/{reponame}/issues",
+ "/{username}/{reponame}/{type:issues}",
+ "/{username}/{reponame}/pulls",
+ "/{username}/{reponame}/{type:pulls}",
+
+ // wiki
+ "/{username}/{reponame}/wiki/",
+
+ // activity
+ "/{username}/{reponame}/activity/",
+ }
+ for _, path := range expensivePaths {
+ if strings.HasPrefix(routePattern, path) {
+ return true
+ }
+ }
+ return false
+}
+
+func isRoutePathForLongPolling(routePattern string) bool {
+ return routePattern == "/user/events"
+}
+
+func determineRequestPriority(reqCtx reqctx.RequestContext) (ret struct {
+ SignedIn bool
+ Expensive bool
+ LongPolling bool
+},
+) {
+ chiRoutePath := chi.RouteContext(reqCtx).RoutePattern()
+ if _, ok := reqCtx.GetData()[middleware.ContextDataKeySignedUser].(*user_model.User); ok {
+ ret.SignedIn = true
+ } else {
+ ret.Expensive = isRoutePathExpensive(chiRoutePath)
+ ret.LongPolling = isRoutePathForLongPolling(chiRoutePath)
+ }
+ return ret
+}
diff --git a/routers/common/blockexpensive_test.go b/routers/common/blockexpensive_test.go
new file mode 100644
index 0000000000..db5c0db7dd
--- /dev/null
+++ b/routers/common/blockexpensive_test.go
@@ -0,0 +1,30 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package common
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestBlockExpensive(t *testing.T) {
+ cases := []struct {
+ expensive bool
+ routePath string
+ }{
+ {false, "/user/xxx"},
+ {false, "/login/xxx"},
+ {true, "/{username}/{reponame}/archive/xxx"},
+ {true, "/{username}/{reponame}/graph"},
+ {true, "/{username}/{reponame}/src/xxx"},
+ {true, "/{username}/{reponame}/wiki/xxx"},
+ {true, "/{username}/{reponame}/activity/xxx"},
+ }
+ for _, c := range cases {
+ assert.Equal(t, c.expensive, isRoutePathExpensive(c.routePath), "routePath: %s", c.routePath)
+ }
+
+ assert.True(t, isRoutePathForLongPolling("/user/events"))
+}
diff --git a/routers/common/db.go b/routers/common/db.go
index cb163c867d..01c0261427 100644
--- a/routers/common/db.go
+++ b/routers/common/db.go
@@ -5,7 +5,7 @@ package common
import (
"context"
- "fmt"
+ "errors"
"time"
"code.gitea.io/gitea/models/db"
@@ -25,7 +25,7 @@ func InitDBEngine(ctx context.Context) (err error) {
for i := 0; i < setting.Database.DBConnectRetries; i++ {
select {
case <-ctx.Done():
- return fmt.Errorf("Aborted due to shutdown:\nin retry ORM engine initialization")
+ return errors.New("Aborted due to shutdown:\nin retry ORM engine initialization")
default:
}
log.Info("ORM engine initialization attempt #%d/%d...", i+1, setting.Database.DBConnectRetries)
diff --git a/routers/common/markup.go b/routers/common/markup.go
index 60bf0dba54..4c77ff33ed 100644
--- a/routers/common/markup.go
+++ b/routers/common/markup.go
@@ -88,7 +88,7 @@ func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, ur
})
rctx = rctx.WithMarkupType("").WithRelativePath(filePath) // render the repo file content by its extension
default:
- ctx.HTTPError(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
+ ctx.HTTPError(http.StatusUnprocessableEntity, "Unknown mode: "+mode)
return
}
rctx = rctx.WithUseAbsoluteLink(true)
diff --git a/routers/common/pagetmpl.go b/routers/common/pagetmpl.go
new file mode 100644
index 0000000000..52c9fceba3
--- /dev/null
+++ b/routers/common/pagetmpl.go
@@ -0,0 +1,75 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package common
+
+import (
+ goctx "context"
+ "errors"
+
+ activities_model "code.gitea.io/gitea/models/activities"
+ "code.gitea.io/gitea/models/db"
+ issues_model "code.gitea.io/gitea/models/issues"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/context"
+)
+
+// StopwatchTmplInfo is a view on a stopwatch specifically for template rendering
+type StopwatchTmplInfo struct {
+ IssueLink string
+ RepoSlug string
+ IssueIndex int64
+ Seconds int64
+}
+
+func getActiveStopwatch(goCtx goctx.Context) *StopwatchTmplInfo {
+ ctx := context.GetWebContext(goCtx)
+ if ctx.Doer == nil {
+ return nil
+ }
+
+ _, sw, issue, err := issues_model.HasUserStopwatch(ctx, ctx.Doer.ID)
+ if err != nil {
+ if !errors.Is(err, goctx.Canceled) {
+ log.Error("Unable to HasUserStopwatch for user:%-v: %v", ctx.Doer, err)
+ }
+ return nil
+ }
+
+ if sw == nil || sw.ID == 0 {
+ return nil
+ }
+
+ return &StopwatchTmplInfo{
+ issue.Link(),
+ issue.Repo.FullName(),
+ issue.Index,
+ sw.Seconds() + 1, // ensure time is never zero in ui
+ }
+}
+
+func notificationUnreadCount(goCtx goctx.Context) int64 {
+ ctx := context.GetWebContext(goCtx)
+ if ctx.Doer == nil {
+ return 0
+ }
+ count, err := db.Count[activities_model.Notification](ctx, activities_model.FindNotificationOptions{
+ UserID: ctx.Doer.ID,
+ Status: []activities_model.NotificationStatus{activities_model.NotificationStatusUnread},
+ })
+ if err != nil {
+ if !errors.Is(err, goctx.Canceled) {
+ log.Error("Unable to find notification for user:%-v: %v", ctx.Doer, err)
+ }
+ return 0
+ }
+ return count
+}
+
+func PageTmplFunctions(ctx *context.Context) {
+ if ctx.IsSigned {
+ // defer the function call to the last moment when the tmpl renders
+ ctx.Data["NotificationUnreadCount"] = notificationUnreadCount
+ ctx.Data["GetActiveStopwatch"] = getActiveStopwatch
+ }
+}