Add new debug option for http

This commit is contained in:
Tom Wiesing 2023-11-22 17:28:46 +01:00
parent 0ba34fe80f
commit 0290a42d07
No known key found for this signature in database
39 changed files with 293 additions and 189 deletions

View file

@ -10,6 +10,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling"
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php/users"
"github.com/tkw1536/pkglib/httpx"
@ -21,6 +22,7 @@ type Next struct {
Auth *auth.Auth
Policy *policy.Policy
Instances *instances.Instances
Handleing *handling.Handling
}
}
@ -72,7 +74,7 @@ func (next *Next) getInstance(r *http.Request) (wisski *wisski.WissKI, path stri
}
func (next *Next) HandleRoute(ctx context.Context, path string) (http.Handler, error) {
return httpx.RedirectHandler(func(r *http.Request) (string, int, error) {
return next.dependencies.Handleing.Redirect(func(r *http.Request) (string, int, error) {
// get the instance and the path
instance, path, err := next.getInstance(r)
if err != nil {

View file

@ -11,25 +11,29 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/tokens"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/ssh2"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/ssh2/sshkeys"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/julienschmidt/httprouter"
"github.com/tkw1536/pkglib/httpx"
"github.com/tkw1536/pkglib/httpx/form"
)
type UserPanel struct {
component.Base
dependencies struct {
Auth *auth.Auth
Auth *auth.Auth
Handling *handling.Handling
Templating *templating.Templating
Policy *policy.Policy
Tokens *tokens.Tokens
Instances *instances.Instances
Next *next.Next
Keys *sshkeys.SSHKeys
SSH2 *ssh2.SSH2
Policy *policy.Policy
Tokens *tokens.Tokens
Instances *instances.Instances
Next *next.Next
Keys *sshkeys.SSHKeys
SSH2 *ssh2.SSH2
}
}
@ -142,12 +146,12 @@ func (panel *UserPanel) HandleRoute(ctx context.Context, route string) (http.Han
type userFormContext struct {
templating.RuntimeFlags
httpx.FormContext
form.FormContext
User *models.User
}
func (panel *UserPanel) UserFormContext(tpl *templating.Template[userFormContext], last component.MenuItem, funcs ...templating.FlagFunc) func(ctx httpx.FormContext, r *http.Request) any {
func (panel *UserPanel) UserFormContext(tpl *templating.Template[userFormContext], last component.MenuItem, funcs ...templating.FlagFunc) func(ctx form.FormContext, r *http.Request) any {
funcs = append(funcs, func(flags templating.Flags, r *http.Request) templating.Flags {
// append the last menu item, and prepend the menuUser one!
flags.Crumbs = append(flags.Crumbs, last, last)
@ -156,7 +160,7 @@ func (panel *UserPanel) UserFormContext(tpl *templating.Template[userFormContext
return flags
})
return func(ctx httpx.FormContext, r *http.Request) any {
return func(ctx form.FormContext, r *http.Request) any {
uctx := userFormContext{FormContext: ctx}
if user, err := panel.dependencies.Auth.UserOfSession(r); err == nil {
uctx.User = &user.User

View file

@ -9,14 +9,14 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
"github.com/tkw1536/pkglib/httpx"
"github.com/tkw1536/pkglib/httpx/field"
"github.com/tkw1536/pkglib/httpx/form"
"github.com/tkw1536/pkglib/httpx/form/field"
)
//go:embed "templates/password.html"
var passwordHTML []byte
var passwordTemplate = templating.Parse[userFormContext](
"password.html", passwordHTML, httpx.FormTemplate,
"password.html", passwordHTML, form.FormTemplate,
templating.Title("Change Password"),
templating.Assets(assets.AssetsUser),
@ -34,17 +34,17 @@ var (
func (panel *UserPanel) routePassword(ctx context.Context) http.Handler {
tpl := passwordTemplate.Prepare(panel.dependencies.Templating)
return &httpx.Form[struct{}]{
return &form.Form[struct{}]{
Fields: []field.Field{
{Name: "old", Type: field.Password, Autocomplete: field.CurrentPassword, EmptyOnError: true, Label: "Current Password"},
{Name: "otp", Type: field.Text, Autocomplete: field.OneTimeCode, EmptyOnError: true, Label: "Current Passcode (optional)"},
{Name: "new", Type: field.Password, Autocomplete: field.NewPassword, EmptyOnError: true, Label: "New Password"},
{Name: "new2", Type: field.Password, Autocomplete: field.NewPassword, EmptyOnError: true, Label: "New Password (again)"},
},
FieldTemplate: field.PureCSSFieldTemplate,
FieldTemplate: assets.PureCSSFieldTemplate,
RenderTemplate: tpl.Template(),
RenderTemplateContext: panel.UserFormContext(tpl, menuChangePassword),
Template: tpl.Template(),
TemplateContext: panel.UserFormContext(tpl, menuChangePassword),
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
old, passcode, new, new2 := values["old"], values["otp"], values["new"], values["new2"]
@ -82,7 +82,7 @@ func (panel *UserPanel) routePassword(ctx context.Context) http.Handler {
return struct{}{}, nil
},
RenderSuccess: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error {
Success: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error {
return errPasswordSet
},
}

View file

@ -13,7 +13,8 @@ import (
"github.com/gliderlabs/ssh"
"github.com/rs/zerolog"
"github.com/tkw1536/pkglib/httpx"
"github.com/tkw1536/pkglib/httpx/field"
"github.com/tkw1536/pkglib/httpx/form"
"github.com/tkw1536/pkglib/httpx/form/field"
gossh "golang.org/x/crypto/ssh"
@ -57,7 +58,7 @@ func (panel *UserPanel) sshRoute(ctx context.Context) http.Handler {
),
)
return tpl.HTMLHandler(func(r *http.Request) (sc SSHTemplateContext, err error) {
return tpl.HTMLHandler(panel.dependencies.Handling, func(r *http.Request) (sc SSHTemplateContext, err error) {
user, err := panel.dependencies.Auth.UserOfSession(r)
if err != nil {
return sc, err
@ -129,7 +130,7 @@ func (panel *UserPanel) sshDeleteRoute(ctx context.Context) http.Handler {
//go:embed "templates/ssh_add.html"
var sshAddHTML []byte
var sshAddTemplate = templating.ParseForm(
"ssh_add.html", sshAddHTML, httpx.FormTemplate,
"ssh_add.html", sshAddHTML, form.FormTemplate,
templating.Title("Add SSH Key"),
templating.Assets(assets.AssetsUser),
)
@ -150,15 +151,15 @@ func (panel *UserPanel) sshAddRoute(ctx context.Context) http.Handler {
),
)
return &httpx.Form[addKeyResult]{
return &form.Form[addKeyResult]{
Fields: []field.Field{
{Name: "comment", Type: field.Text, Label: "Comment"},
{Name: "key", Type: field.Textarea, Label: "Key in authorized_keys format"}, // has hacked css!
},
FieldTemplate: field.PureCSSFieldTemplate,
FieldTemplate: assets.PureCSSFieldTemplate,
RenderTemplate: tpl.Template(),
RenderTemplateContext: templating.FormTemplateContext(tpl),
Template: tpl.Template(),
TemplateContext: templating.FormTemplateContext(tpl),
Validate: func(r *http.Request, values map[string]string) (ak addKeyResult, err error) {
ak.User, err = panel.dependencies.Auth.UserOfSession(r)
@ -181,7 +182,7 @@ func (panel *UserPanel) sshAddRoute(ctx context.Context) http.Handler {
return ak, nil
},
RenderSuccess: func(ak addKeyResult, values map[string]string, w http.ResponseWriter, r *http.Request) error {
Success: func(ak addKeyResult, values map[string]string, w http.ResponseWriter, r *http.Request) error {
// add the key to the user
if err := panel.dependencies.Keys.Add(r.Context(), ak.User.User.User, ak.Comment, ak.Key); err != nil {
return errAddKey

View file

@ -11,7 +11,8 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/rs/zerolog"
"github.com/tkw1536/pkglib/httpx"
"github.com/tkw1536/pkglib/httpx/field"
"github.com/tkw1536/pkglib/httpx/form"
"github.com/tkw1536/pkglib/httpx/form/field"
_ "embed"
)
@ -44,7 +45,7 @@ func (panel *UserPanel) tokensRoute(ctx context.Context) http.Handler {
),
)
return tpl.HTMLHandler(func(r *http.Request) (tc TokenTemplateContext, err error) {
return tpl.HTMLHandler(panel.dependencies.Handling, func(r *http.Request) (tc TokenTemplateContext, err error) {
// list the user
user, err := panel.dependencies.Auth.UserOfSession(r)
if err != nil || user == nil {
@ -94,7 +95,7 @@ func (panel *UserPanel) tokensDeleteRoute(ctx context.Context) http.Handler {
//go:embed "templates/tokens_add.html"
var tokensAddHTML []byte
var tokensAddTemplate = templating.ParseForm(
"tokens_add.html", tokensAddHTML, httpx.FormTemplate,
"tokens_add.html", tokensAddHTML, form.FormTemplate,
templating.Title("Add Token"),
templating.Assets(assets.AssetsUser),
)
@ -108,7 +109,7 @@ type addTokenResult struct {
//go:embed "templates/token_created.html"
var tokenCreatedHTML []byte
var tokenCreateTemplate = templating.Parse[TokenCreateContext](
"token_created.html", tokenCreatedHTML, httpx.FormTemplate,
"token_created.html", tokenCreatedHTML, form.FormTemplate,
templating.Title("Add Token"),
templating.Assets(assets.AssetsUser),
)
@ -139,14 +140,14 @@ func (panel *UserPanel) tokensAddRoute(ctx context.Context) http.Handler {
),
)
return &httpx.Form[addTokenResult]{
return &form.Form[addTokenResult]{
Fields: []field.Field{
{Name: "description", Type: field.Text, Label: "Description"},
},
FieldTemplate: field.PureCSSFieldTemplate,
FieldTemplate: assets.PureCSSFieldTemplate,
RenderTemplate: tplForm.Template(),
RenderTemplateContext: templating.FormTemplateContext(tplForm),
Template: tplForm.Template(),
TemplateContext: templating.FormTemplateContext(tplForm),
Validate: func(r *http.Request, values map[string]string) (at addTokenResult, err error) {
at.User, err = panel.dependencies.Auth.UserOfSession(r)
@ -164,7 +165,7 @@ func (panel *UserPanel) tokensAddRoute(ctx context.Context) http.Handler {
return at, nil
},
RenderSuccess: func(at addTokenResult, values map[string]string, w http.ResponseWriter, r *http.Request) error {
Success: func(at addTokenResult, values map[string]string, w http.ResponseWriter, r *http.Request) error {
// add the key to the user
tok, err := panel.dependencies.Tokens.Add(r.Context(), at.User.User.User, at.Description, at.Scopes)
if err != nil {
@ -175,10 +176,16 @@ func (panel *UserPanel) tokensAddRoute(ctx context.Context) http.Handler {
}
// render the created context
return httpx.WriteHTML(tplDone.Context(r, TokenCreateContext{
Domain: template.URL(panel.Config.HTTP.JoinPath().String()),
Token: tok,
}), nil, tplDone.Template(), "", w, r)
return panel.dependencies.Handling.WriteHTML(
tplDone.Context(r, TokenCreateContext{
Domain: template.URL(panel.Config.HTTP.JoinPath().String()),
Token: tok,
}),
nil,
tplDone.Template(),
w,
r,
)
},
}
}

View file

@ -8,8 +8,8 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
"github.com/tkw1536/pkglib/httpx"
"github.com/tkw1536/pkglib/httpx/field"
"github.com/tkw1536/pkglib/httpx/form"
"github.com/tkw1536/pkglib/httpx/form/field"
_ "embed"
)
@ -17,7 +17,7 @@ import (
//go:embed "templates/totp_enable.html"
var totpEnableHTML []byte
var totpEnable = templating.Parse[userFormContext](
"totp_enable.html", totpEnableHTML, httpx.FormTemplate,
"totp_enable.html", totpEnableHTML, form.FormTemplate,
templating.Title("Enable TOTP"),
templating.Assets(assets.AssetsUser),
@ -26,19 +26,19 @@ var totpEnable = templating.Parse[userFormContext](
func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler {
tpl := totpEnable.Prepare(panel.dependencies.Templating)
return &httpx.Form[struct{}]{
return &form.Form[struct{}]{
Fields: []field.Field{
{Name: "password", Type: field.Password, Autocomplete: field.CurrentPassword, EmptyOnError: true, Label: "Current Password"},
},
FieldTemplate: field.PureCSSFieldTemplate,
FieldTemplate: assets.PureCSSFieldTemplate,
SkipForm: func(r *http.Request) (data struct{}, skip bool) {
Skip: func(r *http.Request) (data struct{}, skip bool) {
user, err := panel.dependencies.Auth.UserOfSession(r)
return struct{}{}, err == nil && user != nil && user.IsTOTPEnabled()
},
RenderTemplate: tpl.Template(),
RenderTemplateContext: panel.UserFormContext(tpl, menuTOTPEnable),
Template: tpl.Template(),
TemplateContext: panel.UserFormContext(tpl, menuTOTPEnable),
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
password := values["password"]
@ -64,7 +64,7 @@ func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler {
return struct{}{}, nil
},
RenderSuccess: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error {
Success: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error {
http.Redirect(w, r, "/user/totp/enroll", http.StatusSeeOther)
return nil
},
@ -74,7 +74,7 @@ func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler {
//go:embed "templates/totp_enroll.html"
var totpEnrollHTML []byte
var totpEnrollTemplate = templating.Parse[totpEnrollContext](
"totp_enroll.html", totpEnrollHTML, httpx.FormTemplate,
"totp_enroll.html", totpEnrollHTML, form.FormTemplate,
templating.Title("Enable TOTP"),
templating.Assets(assets.AssetsUser),
@ -97,18 +97,20 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
),
)
return &httpx.Form[struct{}]{
return &form.Form[struct{}]{
Fields: []field.Field{
{Name: "password", Type: field.Password, Autocomplete: field.CurrentPassword, EmptyOnError: true, Label: "Current Password"},
{Name: "otp", Type: field.Text, Autocomplete: field.OneTimeCode, EmptyOnError: true, Label: "Passcode"},
},
FieldTemplate: field.PureCSSFieldTemplate,
FieldTemplate: assets.PureCSSFieldTemplate,
SkipForm: func(r *http.Request) (data struct{}, skip bool) {
Skip: func(r *http.Request) (data struct{}, skip bool) {
user, err := panel.dependencies.Auth.UserOfSession(r)
return struct{}{}, err == nil && user != nil && user.IsTOTPEnabled()
},
RenderTemplateContext: func(context httpx.FormContext, r *http.Request) any {
Template: tpl.Template(),
TemplateContext: func(context form.FormContext, r *http.Request) any {
user, err := panel.dependencies.Auth.UserOfSession(r)
ctx := totpEnrollContext{
@ -131,7 +133,6 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
return tpl.Context(r, ctx)
},
RenderTemplate: tpl.Template(),
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
password, otp := values["password"], values["otp"]
@ -157,7 +158,7 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
return struct{}{}, nil
},
RenderSuccess: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error {
Success: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error {
http.Redirect(w, r, "/user/", http.StatusSeeOther)
return nil
},
@ -167,7 +168,7 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
//go:embed "templates/totp_disable.html"
var totpDisableHTML []byte
var totpDisableTemplate = templating.Parse[userFormContext](
"totp_disable.html", totpDisableHTML, httpx.FormTemplate,
"totp_disable.html", totpDisableHTML, form.FormTemplate,
templating.Title("Disable TOTP"),
templating.Assets(assets.AssetsUser),
@ -176,19 +177,20 @@ var totpDisableTemplate = templating.Parse[userFormContext](
func (panel *UserPanel) routeTOTPDisable(ctx context.Context) http.Handler {
tpl := totpDisableTemplate.Prepare(panel.dependencies.Templating)
return &httpx.Form[struct{}]{
return &form.Form[struct{}]{
Fields: []field.Field{
{Name: "password", Type: field.Password, Autocomplete: field.CurrentPassword, EmptyOnError: true, Label: "Current Password"},
{Name: "otp", Type: field.Text, Autocomplete: field.OneTimeCode, EmptyOnError: true, Label: "Current Passcode"},
},
FieldTemplate: field.PureCSSFieldTemplate,
FieldTemplate: assets.PureCSSFieldTemplate,
SkipForm: func(r *http.Request) (data struct{}, skip bool) {
Skip: func(r *http.Request) (data struct{}, skip bool) {
user, err := panel.dependencies.Auth.UserOfSession(r)
return struct{}{}, err == nil && user != nil && !user.IsTOTPEnabled()
},
RenderTemplate: tpl.Template(),
RenderTemplateContext: panel.UserFormContext(tpl, menuTOTPDisable),
Template: tpl.Template(),
TemplateContext: panel.UserFormContext(tpl, menuTOTPDisable),
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
password, otp := values["password"], values["otp"]
@ -214,7 +216,7 @@ func (panel *UserPanel) routeTOTPDisable(ctx context.Context) http.Handler {
return struct{}{}, nil
},
RenderSuccess: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error {
Success: func(_ struct{}, values map[string]string, w http.ResponseWriter, r *http.Request) error {
http.Redirect(w, r, "/user/", http.StatusSeeOther)
return nil
},

View file

@ -59,7 +59,7 @@ func (panel *UserPanel) routeUser(ctx context.Context) http.Handler {
templating.Actions(actions...),
)
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (uc userContext, funcs []templating.FlagFunc, err error) {
return tpl.HTMLHandlerWithFlags(panel.dependencies.Handling, func(r *http.Request) (uc userContext, funcs []templating.FlagFunc, err error) {
// find the user
uc.AuthUser, err = panel.dependencies.Auth.UserOfSession(r)
if err != nil || uc.AuthUser == nil {

View file

@ -10,8 +10,8 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
"github.com/tkw1536/pkglib/httpx"
"github.com/tkw1536/pkglib/httpx/field"
"github.com/tkw1536/pkglib/httpx/form"
"github.com/tkw1536/pkglib/httpx/form/field"
"github.com/gorilla/sessions"
@ -162,7 +162,7 @@ func (auth *Auth) Logout(w http.ResponseWriter, r *http.Request) error {
//go:embed "login.html"
var loginHTML []byte
var loginTemplate = templating.ParseForm(
"login.html", loginHTML, httpx.FormTemplate,
"login.html", loginHTML, form.FormTemplate,
templating.Title("Login Required"),
templating.Assets(assets.AssetsUser),
@ -182,21 +182,21 @@ func (auth *Auth) authLogin(ctx context.Context) http.Handler {
},
)
return &httpx.Form[*AuthUser]{
return &form.Form[*AuthUser]{
Fields: []field.Field{
{Name: "username", Type: field.Text, Autocomplete: field.Username, Label: "Username"},
{Name: "password", Type: field.Password, Autocomplete: field.CurrentPassword, EmptyOnError: true, Label: "Password"},
{Name: "otp", Type: field.Text, Autocomplete: field.OneTimeCode, EmptyOnError: true, Label: "Passcode (optional)"},
},
FieldTemplate: field.PureCSSFieldTemplate,
FieldTemplate: assets.PureCSSFieldTemplate,
RenderTemplateContext: func(ctx httpx.FormContext, r *http.Request) any {
Template: tpl.Template(),
TemplateContext: func(ctx form.FormContext, r *http.Request) any {
if ctx.Err != nil {
ctx.Err = errLoginFailed
}
return tpl.Context(r, templating.NewFormContext(ctx))
},
RenderTemplate: tpl.Template(),
Validate: func(r *http.Request, values map[string]string) (*AuthUser, error) {
username, password, passcode := values["username"], values["password"], values["otp"]
@ -215,12 +215,12 @@ func (auth *Auth) authLogin(ctx context.Context) http.Handler {
return user, nil
},
SkipForm: func(r *http.Request) (user *AuthUser, skip bool) {
Skip: func(r *http.Request) (user *AuthUser, skip bool) {
user, err := auth.UserOfSession(r)
return user, err == nil && user != nil
},
RenderSuccess: func(user *AuthUser, _ map[string]string, w http.ResponseWriter, r *http.Request) error {
Success: func(user *AuthUser, _ map[string]string, w http.ResponseWriter, r *http.Request) error {
if err := auth.Login(w, r, user); err != nil {
return err
}

View file

@ -14,9 +14,9 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
"github.com/rs/zerolog"
"github.com/tkw1536/pkglib/httpx"
"github.com/tkw1536/pkglib/lazy"
_ "embed"
@ -27,6 +27,7 @@ type Resolver struct {
dependencies struct {
Instances *instances.Instances
Templating *templating.Templating
Handling *handling.Handling
Auth *auth.Auth
}
@ -106,7 +107,7 @@ func (resolver *Resolver) HandleRoute(ctx context.Context, route string) (http.H
if resolver.dependencies.Auth.CheckScope("", scopes.ScopeUserValid, r) != nil {
ctx.IndexContext.Prefixes = nil
}
httpx.WriteHTML(tpl.Context(r, ctx), nil, t, "", w, r)
resolver.dependencies.Handling.WriteHTML(tpl.Context(r, ctx), nil, t, w, r)
},
Resolver: resolvers.InOrder{

View file

@ -4,7 +4,7 @@ import (
"context"
"net/http"
"github.com/tkw1536/pkglib/mux"
"github.com/tkw1536/pkglib/httpx/mux"
)
// Routeable is a component that is servable
@ -51,10 +51,29 @@ type Routes struct {
Decorator func(http.Handler) http.Handler
}
type routeContextTyp int
const routeContextKey routeContextTyp = 0
// RouteContext represents the context passed to a given route
type RouteContext struct {
DefaultDomain bool
}
// WithRouteContext adds the given RouteContext to the context
func WithRouteContext(parent context.Context, value RouteContext) context.Context {
return context.WithValue(parent, routeContextKey, value)
}
// RouteContextOf returns the route context of the given context
func RouteContextOf(context context.Context) RouteContext {
ctx, ok := context.Value(routeContextKey).(RouteContext)
if !ok {
return RouteContext{}
}
return ctx
}
// Predicate returns the predicate corresponding to the given route
func (routes Routes) Predicate(context func(*http.Request) RouteContext) mux.Predicate {
if routes.MatchAllDomains || routes.Internal {

View file

@ -9,6 +9,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/admin/socket"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
"github.com/julienschmidt/httprouter"
"github.com/rs/zerolog"
@ -20,6 +21,7 @@ import (
type Admin struct {
component.Base
dependencies struct {
Handling *handling.Handling
Fetchers []component.DistilleryFetcher
Instances *instances.Instances
@ -178,7 +180,7 @@ func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http
func (admin *Admin) loginHandler(ctx context.Context) http.Handler {
logger := zerolog.Ctx(ctx)
return httpx.RedirectHandler(func(r *http.Request) (string, int, error) {
return admin.dependencies.Handling.Redirect(func(r *http.Request) (string, int, error) {
// parse the form
if err := r.ParseForm(); err != nil {
logger.Err(err).Msg("failed to parse admin login")

View file

@ -120,7 +120,7 @@ func (admin *Admin) index(ctx context.Context) http.Handler {
),
)
return tpl.HTMLHandler(func(r *http.Request) (idx indexContext, err error) {
return tpl.HTMLHandler(admin.dependencies.Handling, func(r *http.Request) (idx indexContext, err error) {
idx.Distillery, idx.Instances, err = admin.Status(r.Context(), false)
return
})
@ -138,7 +138,7 @@ func (admin *Admin) instances(ctx context.Context) http.Handler {
),
)
return tpl.HTMLHandler(func(r *http.Request) (idx indexContext, err error) {
return tpl.HTMLHandler(admin.dependencies.Handling, func(r *http.Request) (idx indexContext, err error) {
idx.Distillery, idx.Instances, err = admin.Status(r.Context(), true)
return
})

View file

@ -42,7 +42,7 @@ func (admin *Admin) instance(ctx context.Context) http.Handler {
),
)
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ic instanceContext, funcs []templating.FlagFunc, err error) {
return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (ic instanceContext, funcs []templating.FlagFunc, err error) {
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
// find the instance itself!

View file

@ -43,7 +43,7 @@ func (admin *Admin) instanceData(ctx context.Context) http.Handler {
),
)
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ctx instanceDataContext, funcs []templating.FlagFunc, err error) {
return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (ctx instanceDataContext, funcs []templating.FlagFunc, err error) {
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
// setup the context with just the instance

View file

@ -50,7 +50,7 @@ func (admin *Admin) instanceDrupal(ctx context.Context) http.Handler {
),
)
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ctx instanceDrupalContext, funcs []templating.FlagFunc, err error) {
return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (ctx instanceDrupalContext, funcs []templating.FlagFunc, err error) {
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
// setup the context with just the instance

View file

@ -26,7 +26,7 @@ func (admin *Admin) instanceProvision(ctx context.Context) http.Handler {
),
)
return tpl.HTMLHandler(func(r *http.Request) (ipc instanceSystemContext, err error) {
return tpl.HTMLHandler(admin.dependencies.Handling, func(r *http.Request) (ipc instanceSystemContext, err error) {
ipc.prepare(false)
ipc.DefaultProfile = manager.DefaultProfile()
ipc.Profiles = collection.MapValues(manager.Profiles(), func(_ string, profile manager.Profile) string { return profile.Description })

View file

@ -40,7 +40,7 @@ func (admin *Admin) instancePurge(ctx context.Context) http.Handler {
),
)
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ctx instancePurgeContext, funcs []templating.FlagFunc, err error) {
return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (ctx instancePurgeContext, funcs []templating.FlagFunc, err error) {
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
// setup the context with just the instance

View file

@ -65,7 +65,7 @@ func (admin *Admin) instanceRebuild(ctx context.Context) http.Handler {
),
)
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (isc instanceSystemContext, funcs []templating.FlagFunc, err error) {
return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (isc instanceSystemContext, funcs []templating.FlagFunc, err error) {
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
var instance *wisski.WissKI

View file

@ -42,7 +42,7 @@ func (admin *Admin) instanceSnapshots(ctx context.Context) http.Handler {
),
)
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ctx instanceSnapshotsContext, funcs []templating.FlagFunc, err error) {
return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (ctx instanceSnapshotsContext, funcs []templating.FlagFunc, err error) {
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
// setup the context with just the instance

View file

@ -47,7 +47,7 @@ func (admin *Admin) instanceSSH(ctx context.Context) http.Handler {
),
)
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ctx instanceSSHContext, funcs []templating.FlagFunc, err error) {
return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (ctx instanceSSHContext, funcs []templating.FlagFunc, err error) {
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
// setup the context with just the instance

View file

@ -42,7 +42,7 @@ func (admin *Admin) instanceStats(ctx context.Context) http.Handler {
),
)
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ctx instanceStatsContext, funcs []templating.FlagFunc, err error) {
return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (ctx instanceStatsContext, funcs []templating.FlagFunc, err error) {
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
// setup the context with just the instance

View file

@ -15,7 +15,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/status"
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
"github.com/tkw1536/pkglib/httpx"
"github.com/tkw1536/pkglib/httpx/field"
"github.com/tkw1536/pkglib/httpx/form/field"
"github.com/julienschmidt/httprouter"
"golang.org/x/exp/maps"
@ -55,7 +55,7 @@ func (admin *Admin) instanceUsers(ctx context.Context) http.Handler {
),
)
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (instanceUsersContext, []templating.FlagFunc, error) {
return tpl.HTMLHandlerWithFlags(admin.dependencies.Handling, func(r *http.Request) (instanceUsersContext, []templating.FlagFunc, error) {
if r.Method == http.MethodGet {
return admin.getGrantsUsers(r)
} else {

View file

@ -10,8 +10,7 @@ import (
"time"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
"github.com/gorilla/websocket"
"github.com/tkw1536/pkglib/httpx"
"github.com/tkw1536/pkglib/httpx/websocket"
)
// ActionMap handles a set of WebSocket actions
@ -40,7 +39,7 @@ func (err errPanic) Error() string {
// Finally it will send a ResultMessage once handling is complete.
//
// A corresponding client implementation of this can be found in ..../remote/proto.ts
func (am ActionMap) Handle(auth *auth.Auth, conn httpx.WebSocketConnection) (name string, err error) {
func (am ActionMap) Handle(auth *auth.Auth, conn *websocket.Connection) (name string, err error) {
var wg sync.WaitGroup
// once we have finished executing send a binary message (indicating success) to the client.
@ -67,7 +66,7 @@ func (am ActionMap) Handle(auth *auth.Auth, conn httpx.WebSocketConnection) (nam
}
// encode the result message to json!
var message httpx.WebSocketMessage
var message websocket.Message
message.Type = websocket.BinaryMessage
message.Bytes, err = json.Marshal(result)

View file

@ -14,7 +14,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/admin/socket/actions"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/admin/socket/proto"
"github.com/rs/zerolog"
"github.com/tkw1536/pkglib/httpx"
"github.com/tkw1536/pkglib/httpx/websocket"
"github.com/tkw1536/pkglib/lazy"
)
@ -48,14 +48,14 @@ func (socket *Sockets) Routes() component.Routes {
}
func (sockets *Sockets) HandleRoute(ctx context.Context, path string) (http.Handler, error) {
return &httpx.WebSocket{
return &websocket.Server{
Context: ctx,
Handler: sockets.Serve,
}, nil
}
// Serve handles a connection to the websocket api
func (socket *Sockets) Serve(conn httpx.WebSocketConnection) {
func (socket *Sockets) Serve(conn *websocket.Connection) {
// handle the websocket connection!
name, err := socket.actions.Get(func() proto.ActionMap { return socket.Actions(conn.Context()) }).Handle(socket.dependencies.Auth, conn)
if err != nil {

View file

@ -11,7 +11,8 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
"github.com/rs/zerolog"
"github.com/tkw1536/pkglib/httpx"
"github.com/tkw1536/pkglib/httpx/field"
"github.com/tkw1536/pkglib/httpx/form"
"github.com/tkw1536/pkglib/httpx/form/field"
_ "embed"
)
@ -43,7 +44,7 @@ func (admin *Admin) users(ctx context.Context) http.Handler {
),
)
return tpl.HTMLHandler(func(r *http.Request) (uc usersContext, err error) {
return tpl.HTMLHandler(admin.dependencies.Handling, func(r *http.Request) (uc usersContext, err error) {
uc.Error = r.URL.Query().Get("error")
uc.Users, err = admin.dependencies.Auth.Users(r.Context())
return
@ -53,7 +54,7 @@ func (admin *Admin) users(ctx context.Context) http.Handler {
//go:embed "html/user_create.html"
var userCreateHTML []byte
var userCreateTemplate = templating.ParseForm(
"user_create.html", userCreateHTML, httpx.FormTemplate,
"user_create.html", userCreateHTML, form.FormTemplate,
templating.Title("Create User"),
templating.Assets(assets.AssetsAdmin),
@ -80,16 +81,16 @@ func (admin *Admin) createUser(ctx context.Context) http.Handler {
),
)
return &httpx.Form[createUserResult]{
return &form.Form[createUserResult]{
Fields: []field.Field{
{Name: "username", Type: field.Text, Autocomplete: field.Username, Label: "Username"},
{Name: "password", Type: field.Password, Autocomplete: field.NewPassword, Label: "Password"},
{Name: "admin", Type: field.Checkbox, Label: "Distillery Administrator"},
},
FieldTemplate: field.PureCSSFieldTemplate,
FieldTemplate: assets.PureCSSFieldTemplate,
RenderTemplate: tpl.Template(),
RenderTemplateContext: templating.FormTemplateContext(tpl),
Template: tpl.Template(),
TemplateContext: templating.FormTemplateContext(tpl),
Validate: func(r *http.Request, values map[string]string) (cu createUserResult, err error) {
cu.User, cu.Passsword, cu.Admin = values["username"], values["password"], values["admin"] == field.CheckboxChecked
@ -110,7 +111,7 @@ func (admin *Admin) createUser(ctx context.Context) http.Handler {
return cu, nil
},
RenderSuccess: func(cu createUserResult, values map[string]string, w http.ResponseWriter, r *http.Request) error {
Success: func(cu createUserResult, values map[string]string, w http.ResponseWriter, r *http.Request) error {
// create the user
user, err := admin.dependencies.Auth.CreateUser(r.Context(), cu.User)
if err != nil {

View file

@ -21,4 +21,14 @@ type Assets struct {
Styles template.HTML // <link> tags inserted by the asset
}
var PureCSSFieldTemplate = template.Must(template.New("").Parse(`
<div class="pure-control-group">
<label for="{{.Name}}">{{.Label}}</label>
{{ if (eq .Type "textarea" )}}
<textarea name="{{.Name}}" id="{{.Name}}" placeholder="{{.Placeholder}}"{{if .Autocomplete }} autocomplete="{{.Autocomplete}}" {{end}}>{{.Value}}</textarea>
{{ else }}
<input type="{{.Type}}" value="{{.Value}}" name="{{.Name}}" id="{{.Name}}" placeholder="{{.Placeholder}}"{{if .Autocomplete }} autocomplete="{{.Autocomplete}}" {{end}}>
{{ end }}
</div>`))
//go:generate node build.mjs Default User Admin AdminProvision AdminRebuild

View file

@ -0,0 +1,60 @@
package handling
import (
"html/template"
"net/http"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/rs/zerolog"
"github.com/tkw1536/pkglib/httpx"
"github.com/tkw1536/pkglib/httpx/content"
"github.com/tkw1536/pkglib/lazy"
)
type Handling struct {
component.Base
text lazy.Lazy[httpx.ErrInterceptor]
html lazy.Lazy[httpx.ErrInterceptor]
}
func (h *Handling) TextInterceptor() httpx.ErrInterceptor {
return h.text.Get(func() httpx.ErrInterceptor {
return h.interceptor(httpx.TextInterceptor)
})
}
func (h *Handling) HTMLInterceptor() httpx.ErrInterceptor {
return h.html.Get(func() httpx.ErrInterceptor {
return h.interceptor(httpx.TextInterceptor)
})
}
// Interceptor returns a copy of the parent interceptor with global distillery interceptor options enabled.
func (h *Handling) interceptor(parent httpx.ErrInterceptor) httpx.ErrInterceptor {
pf := parent.OnFallback
if pf == nil {
pf = func(r *http.Request, err error) {}
}
parent.RenderError = h.Config.HTTP.Debug.Set && h.Config.HTTP.Debug.Value
parent.OnFallback = func(r *http.Request, err error) {
pf(r, err)
zerolog.Ctx(r.Context()).
Err(err).
Str("path", r.URL.Path).
Msg("unknown error")
}
return parent
}
func (h *Handling) Redirect(Handler content.RedirectFunc) http.Handler {
r := content.Redirect(Handler)
r.Interceptor = h.TextInterceptor()
return r
}
func (h *Handling) WriteHTML(context any, err error, template *template.Template, w http.ResponseWriter, r *http.Request) error {
return content.WriteHTMLI(context, err, template, h.HTMLInterceptor(), w, r)
}

View file

@ -6,6 +6,7 @@ import (
"net/http"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/list"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
)
@ -15,6 +16,7 @@ type Home struct {
dependencies struct {
ListInstances *list.ListInstances
Templating *templating.Templating
Handling *handling.Handling
}
}

View file

@ -61,7 +61,7 @@ func (home *Home) publicHandler(ctx context.Context) http.Handler {
about := home.dependencies.Templating.GetCustomizable(aboutTemplate)
return tpl.HTMLHandler(func(r *http.Request) (pc publicContext, err error) {
return tpl.HTMLHandler(home.dependencies.Handling, func(r *http.Request) (pc publicContext, err error) {
// only act on the root path!
if strings.TrimSuffix(r.URL.Path, "/") != "" {
return pc, httpx.ErrNotFound

View file

@ -8,6 +8,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
_ "embed"
@ -18,6 +19,7 @@ type Legal struct {
dependencies struct {
Static *assets.Static
Templating *templating.Templating
Handling *handling.Handling
}
}
@ -65,7 +67,7 @@ func (legal *Legal) HandleRoute(ctx context.Context, route string) (http.Handler
),
)
return tpl.HTMLHandler(func(r *http.Request) (lc legalContext, err error) {
return tpl.HTMLHandler(legal.dependencies.Handling, func(r *http.Request) (lc legalContext, err error) {
lc.LegalNotices = cli.LegalNotices
lc.CSRFCookie = server.CSRFCookie

View file

@ -11,6 +11,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
"github.com/rs/zerolog"
"github.com/yuin/goldmark"
@ -23,6 +24,7 @@ type News struct {
component.Base
dependencies struct {
Templating *templating.Templating
Handling *handling.Handling
}
}
@ -143,7 +145,7 @@ func (news *News) HandleRoute(ctx context.Context, path string) (http.Handler, e
zerolog.Ctx(ctx).Err(itemsErr).Msg("Unable to load news items")
}
return tpl.HTMLHandler(func(r *http.Request) (nc newsContext, err error) {
return tpl.HTMLHandler(news.dependencies.Handling, func(r *http.Request) (nc newsContext, err error) {
nc.Items, err = items, itemsErr
return
}), nil

View file

@ -2,18 +2,17 @@ package server
import (
"context"
"fmt"
"io"
"net/http"
"runtime/debug"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/tkw1536/pkglib/contextx"
"github.com/tkw1536/pkglib/httpx"
"github.com/tkw1536/pkglib/httpx/timewrap"
"github.com/tkw1536/pkglib/mux"
"github.com/tkw1536/pkglib/httpx/mux"
"github.com/tkw1536/pkglib/httpx/wrap"
"github.com/tkw1536/pkglib/recovery"
"github.com/gorilla/csrf"
"github.com/rs/zerolog"
@ -27,6 +26,7 @@ type Server struct {
Cronables []component.Cronable
Templating *templating.Templating
Handleing *handling.Handling
}
}
@ -39,30 +39,33 @@ var (
//
// Logging messages are directed to progress
func (server *Server) Server(ctx context.Context, progress io.Writer) (public http.Handler, internal http.Handler, err error) {
logger := zerolog.Ctx(ctx)
interceptor := server.dependencies.Handleing.TextInterceptor()
var publicM, internalM mux.Mux[component.RouteContext]
publicM.Context = func(r *http.Request) component.RouteContext {
slug, ok := server.Config.HTTP.NormSlugFromHost(r.Host)
return component.RouteContext{
DefaultDomain: slug == "" && ok,
}
}
publicM.Panic = func(p any, stack []byte, w http.ResponseWriter, r *http.Request) {
// log the panic
logger.Error().
Str("panic", fmt.Sprint(p)).
Str("stack", string(stack)).
Str("path", r.URL.Path).
Msg("panic serving handler")
// wrapHandler wraps individual handlers for errors
wrapHandler := func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// handle any panic()s that occur
defer func() {
// intercept any panic() that wasn't caught
if err := recovery.Recover(recover()); err != nil {
interceptor.Intercept(w, r, err)
}
}()
// and send an internal server error
httpx.TextInterceptor.Fallback.ServeHTTP(w, r)
// determine if we are on a slug from a host
slug, ok := server.Config.HTTP.NormSlugFromHost(r.Host)
rctx := component.WithRouteContext(r.Context(), component.RouteContext{
DefaultDomain: slug == "" && ok,
})
ctx := contextx.WithValuesOf(rctx, ctx)
// serve with the next context
h.ServeHTTP(w, r.WithContext(ctx))
})
}
// setup the internal server identically
internalM.Panic = publicM.Panic
internalM.Context = publicM.Context
var publicM, internalM mux.Mux
// create a csrf protector
csrfProtector := server.csrf()
@ -95,7 +98,7 @@ func (server *Server) Server(ctx context.Context, progress io.Writer) (public ht
handler = routes.Decorate(handler, csrfProtector)
// determine the predicate
predicate := routes.Predicate(publicM.ContextOf)
predicate := routes.Predicate(func(r *http.Request) component.RouteContext { return component.RouteContextOf(r.Context()) })
// and add all the prefixes
for _, prefix := range append([]string{routes.Prefix}, routes.Aliases...) {
@ -107,15 +110,15 @@ func (server *Server) Server(ctx context.Context, progress io.Writer) (public ht
}
}
// apply the given context function
public = httpx.WithContextWrapper(&publicM, func(rcontext context.Context) context.Context { return contextx.WithValuesOf(rcontext, ctx) })
internal = httpx.WithContextWrapper(&internalM, func(rcontext context.Context) context.Context { return contextx.WithValuesOf(rcontext, ctx) })
// wrap the handlers
public = wrapHandler(&publicM)
internal = wrapHandler(&internalM)
// Add Content-Security-Policy
public = WithCSP(public, models.ContentSecurityPolicyDistilery)
internal = WithCSP(internal, models.ContentSecurityPolicyNothing)
public = timewrap.Wrap(public)
public = wrap.Time(public)
err = nil
return
@ -141,10 +144,3 @@ func WithCSP(handler http.Handler, policy string) http.Handler {
handler.ServeHTTP(w, r)
})
}
func init() {
httpx.InterceptorOnFallback = func(req *http.Request, err error) {
stack := debug.Stack()
zerolog.Ctx(req.Context()).Err(err).Str("stack", string(stack)).Msg("unknown error intercepted")
}
}

View file

@ -11,10 +11,12 @@ import (
"strings"
"time"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/handling"
"github.com/gorilla/csrf"
"github.com/rs/zerolog"
"github.com/tkw1536/pkglib/httpx"
"github.com/tkw1536/pkglib/httpx/timewrap"
"github.com/tkw1536/pkglib/httpx/content"
"github.com/tkw1536/pkglib/httpx/form"
"github.com/tkw1536/pkglib/httpx/wrap"
)
//go:embed "src/base.html"
@ -46,7 +48,7 @@ func (tpl *Template[C]) context(r *http.Request, funcs ...FlagFunc) (ctx *tConte
// setup the basic properties
ctx.ctx = r.Context()
ctx.Runtime.RequestURI = r.URL.RequestURI()
ctx.Runtime.StartedAt = timewrap.Start(r).UTC()
ctx.Runtime.StartedAt = wrap.TimeStart(r).UTC()
ctx.Runtime.GeneratedAt = time.Now().UTC()
ctx.Runtime.CSRF = csrf.TemplateField(r)
ctx.Runtime.Menu = tpl.templating.buildMenu(r)
@ -70,19 +72,19 @@ func (tpl *Template[C]) context(r *http.Request, funcs ...FlagFunc) (ctx *tConte
var ParseForm = Parse[FormContext]
type FormContext struct {
httpx.FormContext
form.FormContext
RuntimeFlags
}
// NewFormContext returns a new FormContext from an underlying context
func NewFormContext(context httpx.FormContext) FormContext {
func NewFormContext(context form.FormContext) FormContext {
return FormContext{FormContext: context}
}
// FormTemplateContext returns a new handler for a form with the given base context
func FormTemplateContext(tw *Template[FormContext]) func(ctx httpx.FormContext, r *http.Request) any {
func FormTemplateContext(tw *Template[FormContext]) func(ctx form.FormContext, r *http.Request) any {
// TODO: Is this needed?
return func(ctx httpx.FormContext, r *http.Request) any {
return func(ctx form.FormContext, r *http.Request) any {
return tw.Context(r, FormContext{FormContext: ctx})
}
}
@ -113,19 +115,21 @@ func (tw *Template[C]) HandlerWithFlags(worker func(r *http.Request) (C, []FlagF
// HTMLHandler creates a new httpx.HTMLHandler that calls tw.Handler(worker) and tw.Template.
// See also Handler.
func (tw *Template[C]) HTMLHandler(worker func(r *http.Request) (C, error)) httpx.HTMLHandler[any] {
return httpx.HTMLHandler[any]{
Handler: tw.Handler(worker),
Template: tw.Template(),
func (tw *Template[C]) HTMLHandler(handling *handling.Handling, worker func(r *http.Request) (C, error)) content.HTMLHandler[any] {
return content.HTMLHandler[any]{
Handler: tw.Handler(worker),
Template: tw.Template(),
Interceptor: handling.HTMLInterceptor(),
}
}
// HTMLHandlerWithFlags creates a new httpx.HTMLHandler that calls tw.HandlerWithFlags(worker) and tw.Template.
// See also HandlerWithFlags.
func (tw *Template[C]) HTMLHandlerWithFlags(worker func(r *http.Request) (C, []FlagFunc, error)) httpx.HTMLHandler[any] {
return httpx.HTMLHandler[any]{
Handler: tw.HandlerWithFlags(worker),
Template: tw.Template(),
func (tw *Template[C]) HTMLHandlerWithFlags(handling *handling.Handling, worker func(r *http.Request) (C, []FlagFunc, error)) content.HTMLHandler[any] {
return content.HTMLHandler[any]{
Handler: tw.HandlerWithFlags(worker),
Template: tw.Template(),
Interceptor: handling.HTMLInterceptor(),
}
}

View file

@ -5,7 +5,7 @@ import (
"net/http"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/tkw1536/pkglib/mux"
"github.com/tkw1536/pkglib/httpx/mux"
"golang.org/x/exp/slices"
)