mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-05-31 20:02:09 +00:00
Move context from modules to services (#29440)
Since `modules/context` has to depend on `models` and many other packages, it should be moved from `modules/context` to `services/context` according to design principles. There is no logic code change on this PR, only move packages. - Move `code.gitea.io/gitea/modules/context` to `code.gitea.io/gitea/services/context` - Move `code.gitea.io/gitea/modules/contexttest` to `code.gitea.io/gitea/services/contexttest` because of depending on context - Move `code.gitea.io/gitea/modules/upload` to `code.gitea.io/gitea/services/context/upload` because of depending on context (cherry picked from commit 29f149bd9f517225a3c9f1ca3fb0a7b5325af696) Conflicts: routers/api/packages/alpine/alpine.go routers/api/v1/repo/issue_reaction.go routers/install/install.go routers/web/admin/config.go routers/web/passkey.go routers/web/repo/search.go routers/web/repo/setting/default_branch.go routers/web/user/home.go routers/web/user/profile.go tests/integration/editor_test.go tests/integration/integration_test.go tests/integration/mirror_push_test.go trivial context conflicts also modified all other occurrences in Forgejo specific files
This commit is contained in:
parent
75f7be2aac
commit
894d9b2836
373 changed files with 384 additions and 390 deletions
257
services/context/context.go
Normal file
257
services/context/context.go
Normal file
|
@ -0,0 +1,257 @@
|
|||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
mc "code.gitea.io/gitea/modules/cache"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/httpcache"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
web_types "code.gitea.io/gitea/modules/web/types"
|
||||
|
||||
"gitea.com/go-chi/cache"
|
||||
"gitea.com/go-chi/session"
|
||||
)
|
||||
|
||||
// Render represents a template render
|
||||
type Render interface {
|
||||
TemplateLookup(tmpl string, templateCtx context.Context) (templates.TemplateExecutor, error)
|
||||
HTML(w io.Writer, status int, name string, data any, templateCtx context.Context) error
|
||||
}
|
||||
|
||||
// Context represents context of a request.
|
||||
type Context struct {
|
||||
*Base
|
||||
|
||||
TemplateContext TemplateContext
|
||||
|
||||
Render Render
|
||||
PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData`
|
||||
|
||||
Cache cache.Cache
|
||||
Csrf CSRFProtector
|
||||
Flash *middleware.Flash
|
||||
Session session.Store
|
||||
|
||||
Link string // current request URL (without query string)
|
||||
|
||||
Doer *user_model.User // current signed-in user
|
||||
IsSigned bool
|
||||
IsBasicAuth bool
|
||||
|
||||
ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer
|
||||
|
||||
Repo *Repository
|
||||
Org *Organization
|
||||
Package *Package
|
||||
}
|
||||
|
||||
type TemplateContext map[string]any
|
||||
|
||||
func init() {
|
||||
web.RegisterResponseStatusProvider[*Context](func(req *http.Request) web_types.ResponseStatusProvider {
|
||||
return req.Context().Value(WebContextKey).(*Context)
|
||||
})
|
||||
}
|
||||
|
||||
type webContextKeyType struct{}
|
||||
|
||||
var WebContextKey = webContextKeyType{}
|
||||
|
||||
func GetWebContext(req *http.Request) *Context {
|
||||
ctx, _ := req.Context().Value(WebContextKey).(*Context)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// ValidateContext is a special context for form validation middleware. It may be different from other contexts.
|
||||
type ValidateContext struct {
|
||||
*Base
|
||||
}
|
||||
|
||||
// GetValidateContext gets a context for middleware form validation
|
||||
func GetValidateContext(req *http.Request) (ctx *ValidateContext) {
|
||||
if ctxAPI, ok := req.Context().Value(apiContextKey).(*APIContext); ok {
|
||||
ctx = &ValidateContext{Base: ctxAPI.Base}
|
||||
} else if ctxWeb, ok := req.Context().Value(WebContextKey).(*Context); ok {
|
||||
ctx = &ValidateContext{Base: ctxWeb.Base}
|
||||
} else {
|
||||
panic("invalid context, expect either APIContext or Context")
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func NewTemplateContextForWeb(ctx *Context) TemplateContext {
|
||||
tmplCtx := NewTemplateContext(ctx)
|
||||
tmplCtx["Locale"] = ctx.Base.Locale
|
||||
tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx)
|
||||
return tmplCtx
|
||||
}
|
||||
|
||||
func NewWebContext(base *Base, render Render, session session.Store) *Context {
|
||||
ctx := &Context{
|
||||
Base: base,
|
||||
Render: render,
|
||||
Session: session,
|
||||
|
||||
Cache: mc.GetCache(),
|
||||
Link: setting.AppSubURL + strings.TrimSuffix(base.Req.URL.EscapedPath(), "/"),
|
||||
Repo: &Repository{PullRequest: &PullRequest{}},
|
||||
Org: &Organization{},
|
||||
}
|
||||
ctx.TemplateContext = NewTemplateContextForWeb(ctx)
|
||||
ctx.Flash = &middleware.Flash{DataStore: ctx, Values: url.Values{}}
|
||||
return ctx
|
||||
}
|
||||
|
||||
// Contexter initializes a classic context for a request.
|
||||
func Contexter() func(next http.Handler) http.Handler {
|
||||
rnd := templates.HTMLRenderer()
|
||||
csrfOpts := CsrfOptions{
|
||||
Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()),
|
||||
Cookie: setting.CSRFCookieName,
|
||||
SetCookie: true,
|
||||
Secure: setting.SessionConfig.Secure,
|
||||
CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
|
||||
Header: "X-Csrf-Token",
|
||||
CookieDomain: setting.SessionConfig.Domain,
|
||||
CookiePath: setting.SessionConfig.CookiePath,
|
||||
SameSite: setting.SessionConfig.SameSite,
|
||||
}
|
||||
if !setting.IsProd {
|
||||
CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose
|
||||
}
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
base, baseCleanUp := NewBaseContext(resp, req)
|
||||
defer baseCleanUp()
|
||||
ctx := NewWebContext(base, rnd, session.GetSession(req))
|
||||
|
||||
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
|
||||
ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this
|
||||
ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
|
||||
ctx.Data["Link"] = ctx.Link
|
||||
|
||||
// PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
|
||||
ctx.PageData = map[string]any{}
|
||||
ctx.Data["PageData"] = ctx.PageData
|
||||
|
||||
ctx.Base.AppendContextValue(WebContextKey, ctx)
|
||||
ctx.Base.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
|
||||
|
||||
ctx.Csrf = PrepareCSRFProtector(csrfOpts, ctx)
|
||||
|
||||
// Get the last flash message from cookie
|
||||
lastFlashCookie := middleware.GetSiteCookie(ctx.Req, CookieNameFlash)
|
||||
if vals, _ := url.ParseQuery(lastFlashCookie); len(vals) > 0 {
|
||||
// store last Flash message into the template data, to render it
|
||||
ctx.Data["Flash"] = &middleware.Flash{
|
||||
DataStore: ctx,
|
||||
Values: vals,
|
||||
ErrorMsg: vals.Get("error"),
|
||||
SuccessMsg: vals.Get("success"),
|
||||
InfoMsg: vals.Get("info"),
|
||||
WarningMsg: vals.Get("warning"),
|
||||
}
|
||||
}
|
||||
|
||||
// if there are new messages in the ctx.Flash, write them into cookie
|
||||
ctx.Resp.Before(func(resp ResponseWriter) {
|
||||
if val := ctx.Flash.Encode(); val != "" {
|
||||
middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, val, 0)
|
||||
} else if lastFlashCookie != "" {
|
||||
middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, "", -1)
|
||||
}
|
||||
})
|
||||
|
||||
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
|
||||
if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
|
||||
if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
|
||||
ctx.ServerError("ParseMultipartForm", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform")
|
||||
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||
|
||||
ctx.Data["CsrfToken"] = ctx.Csrf.GetToken()
|
||||
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
|
||||
|
||||
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
|
||||
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
|
||||
ctx.Data["DisableStars"] = setting.Repository.DisableStars
|
||||
ctx.Data["DisableForks"] = setting.Repository.DisableForks
|
||||
ctx.Data["EnableActions"] = setting.Actions.Enabled
|
||||
|
||||
ctx.Data["ManifestData"] = setting.ManifestData
|
||||
|
||||
ctx.Data["UnitWikiGlobalDisabled"] = unit.TypeWiki.UnitGlobalDisabled()
|
||||
ctx.Data["UnitIssuesGlobalDisabled"] = unit.TypeIssues.UnitGlobalDisabled()
|
||||
ctx.Data["UnitPullsGlobalDisabled"] = unit.TypePullRequests.UnitGlobalDisabled()
|
||||
ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled()
|
||||
ctx.Data["UnitActionsGlobalDisabled"] = unit.TypeActions.UnitGlobalDisabled()
|
||||
|
||||
ctx.Data["AllLangs"] = translation.AllLangs()
|
||||
|
||||
next.ServeHTTP(ctx.Resp, ctx.Req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// HasError returns true if error occurs in form validation.
|
||||
// Attention: this function changes ctx.Data and ctx.Flash
|
||||
func (ctx *Context) HasError() bool {
|
||||
hasErr, ok := ctx.Data["HasError"]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
ctx.Flash.ErrorMsg = ctx.GetErrMsg()
|
||||
ctx.Data["Flash"] = ctx.Flash
|
||||
return hasErr.(bool)
|
||||
}
|
||||
|
||||
// GetErrMsg returns error message in form validation.
|
||||
func (ctx *Context) GetErrMsg() string {
|
||||
msg, _ := ctx.Data["ErrorMsg"].(string)
|
||||
if msg == "" {
|
||||
msg = "invalid form data"
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func (ctx *Context) JSONRedirect(redirect string) {
|
||||
ctx.JSON(http.StatusOK, map[string]any{"redirect": redirect})
|
||||
}
|
||||
|
||||
func (ctx *Context) JSONOK() {
|
||||
ctx.JSON(http.StatusOK, map[string]any{"ok": true}) // this is only a dummy response, frontend seldom uses it
|
||||
}
|
||||
|
||||
func (ctx *Context) JSONError(msg any) {
|
||||
switch v := msg.(type) {
|
||||
case string:
|
||||
ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "text"})
|
||||
case template.HTML:
|
||||
ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "html"})
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported type: %T", msg))
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue