diff options
Diffstat (limited to 'routers/common')
-rw-r--r-- | routers/common/actions.go | 71 | ||||
-rw-r--r-- | routers/common/blockexpensive.go | 90 | ||||
-rw-r--r-- | routers/common/blockexpensive_test.go | 30 | ||||
-rw-r--r-- | routers/common/db.go | 4 | ||||
-rw-r--r-- | routers/common/markup.go | 2 | ||||
-rw-r--r-- | routers/common/pagetmpl.go | 75 |
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 + } +} |