Initial commit
This commit is contained in:
commit
57eaeea310
8
.idea/maf-webserver.iml
generated
Normal file
8
.idea/maf-webserver.iml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/maf-webserver.iml" filepath="$PROJECT_DIR$/.idea/maf-webserver.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
9
.idea/workspace.xml
generated
Normal file
9
.idea/workspace.xml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectViewState">
|
||||
<option name="autoscrollFromSource" value="true" />
|
||||
<option name="autoscrollToSource" value="true" />
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
</project>
|
5
common.go
Normal file
5
common.go
Normal file
@ -0,0 +1,5 @@
|
||||
package webserver
|
||||
|
||||
import "github.com/labstack/echo/v4"
|
||||
|
||||
type MiddlewareFunc = echo.MiddlewareFunc
|
15
config.go
Normal file
15
config.go
Normal file
@ -0,0 +1,15 @@
|
||||
package webserver
|
||||
|
||||
type Config struct {
|
||||
Port uint16 `yaml:"port" envconfig:"PORT"`
|
||||
BaseUrl string `yaml:"baseUrl" envconfig:"BASE_URL"`
|
||||
ReloadTemplates bool `yaml:"reloadTemplates" envconfig:"RELOAD_TEMPLATES"`
|
||||
}
|
||||
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
Port: 8080,
|
||||
BaseUrl: "",
|
||||
ReloadTemplates: false,
|
||||
}
|
||||
}
|
149
context.go
Normal file
149
context.go
Normal file
@ -0,0 +1,149 @@
|
||||
package webserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type HandlerFunc func(c *Context) error
|
||||
|
||||
type Context struct {
|
||||
echo.Context
|
||||
csrfConfig *middleware.CSRFConfig
|
||||
}
|
||||
|
||||
func CustomContext(csrfConfig *middleware.CSRFConfig) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
cc := &Context{
|
||||
Context: c,
|
||||
csrfConfig: csrfConfig,
|
||||
}
|
||||
return next(cc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) View(code int, filename string, obj interface{}) error {
|
||||
if mapContext, ok := obj.(map[string]interface{}); ok {
|
||||
viewData := mapContext
|
||||
viewData["request"] = c.Request()
|
||||
viewData["csrf"] = c.CSRF()
|
||||
obj = viewData
|
||||
}
|
||||
return c.Context.Render(code, filename, obj)
|
||||
}
|
||||
|
||||
func (c *Context) DtoError(err error) error {
|
||||
|
||||
switch err.(type) {
|
||||
case *NotFoundError:
|
||||
return echo.NewHTTPError(http.StatusNotFound, "Not found")
|
||||
case *InternalError:
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Internal server error")
|
||||
case *InvalidRequestError:
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Bad request")
|
||||
default:
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Internal server error")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c *Context) NotFound() error {
|
||||
return echo.NewHTTPError(http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (c *Context) InternalServerError(err ...error) error {
|
||||
if err != nil && len(err) > 0 {
|
||||
slog.Error("", err)
|
||||
}
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func Handle(handler HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return handler(c.(*Context))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) CSRF() string {
|
||||
return c.Get(middleware.DefaultCSRFConfig.ContextKey).(string)
|
||||
}
|
||||
|
||||
func (c *Context) GetFormData() (map[string]string, error) {
|
||||
|
||||
var err error
|
||||
ret := make(map[string]string)
|
||||
|
||||
//c.MultipartForm()
|
||||
contentType := c.Request().Header.Get("Content-Type")
|
||||
if contentType == "multipart/form-data" {
|
||||
err = c.Request().ParseMultipartForm(4 * 1024 * 1024)
|
||||
} else if contentType == "application/x-www-form-urlencoded" {
|
||||
err = c.Request().ParseForm()
|
||||
} else {
|
||||
err = errors.New("unsupported content type")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range c.Request().PostForm {
|
||||
ret[k] = v[len(v)-1]
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
/*
|
||||
func requestQueryToFilter(c *Context, params map[string]string) (i2bolt.Filter, map[string]string) {
|
||||
|
||||
var subFilters []i2bolt.Filter
|
||||
filterParams := make(map[string]string)
|
||||
|
||||
query := c.Request().URL.Query()
|
||||
for param, fieldMethod := range params {
|
||||
|
||||
if v, ok := query[param]; ok {
|
||||
value := strings.TrimSpace(v[0])
|
||||
if value != "" {
|
||||
|
||||
parts := strings.Split(fieldMethod, "__")
|
||||
fieldName := parts[0]
|
||||
method := ""
|
||||
if len(parts) > 1 {
|
||||
method = parts[1]
|
||||
}
|
||||
|
||||
var valueIntf interface{}
|
||||
valueIntf = value
|
||||
|
||||
if method == "is" {
|
||||
valueIntf = value == "yes"
|
||||
}
|
||||
|
||||
fieldNames := strings.Split(fieldName, "|")
|
||||
for _, name := range fieldNames {
|
||||
subFilters = append(subFilters, i2bolt.Q(name, method, valueIntf))
|
||||
filterParams[param] = value
|
||||
}
|
||||
|
||||
} else {
|
||||
filterParams[param] = ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var filter i2bolt.Filter
|
||||
if len(subFilters) > 0 {
|
||||
filter = i2bolt.And(subFilters...)
|
||||
}
|
||||
|
||||
return filter, filterParams
|
||||
}
|
||||
*/
|
70
dto.go
Normal file
70
dto.go
Normal file
@ -0,0 +1,70 @@
|
||||
package webserver
|
||||
|
||||
import "fmt"
|
||||
|
||||
type GateError struct {
|
||||
Message string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *GateError) Error() string {
|
||||
if e.Message != "" && e.Err != nil {
|
||||
return fmt.Sprintf("%s: %v", e.Message, e.Err)
|
||||
} else if e.Message != "" {
|
||||
return e.Message
|
||||
} else {
|
||||
return e.Err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
type InputError struct {
|
||||
GateError
|
||||
}
|
||||
|
||||
func NewInputError(message string, err error) *InputError {
|
||||
return &InputError{
|
||||
GateError{
|
||||
Message: message,
|
||||
Err: err,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type InternalError struct {
|
||||
GateError
|
||||
}
|
||||
|
||||
func NewInternalError(message string, err error) *InternalError {
|
||||
return &InternalError{
|
||||
GateError{
|
||||
Message: message,
|
||||
Err: err,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type NotFoundError struct {
|
||||
GateError
|
||||
}
|
||||
|
||||
func NewNotFoundError(message string, err error) *NotFoundError {
|
||||
return &NotFoundError{
|
||||
GateError{
|
||||
Message: message,
|
||||
Err: err,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type InvalidRequestError struct {
|
||||
GateError
|
||||
}
|
||||
|
||||
func NewInvalidRequestError(message string, err error) *InvalidRequestError {
|
||||
return &InvalidRequestError{
|
||||
GateError{
|
||||
Message: message,
|
||||
Err: err,
|
||||
},
|
||||
}
|
||||
}
|
30
go.mod
Normal file
30
go.mod
Normal file
@ -0,0 +1,30 @@
|
||||
module netgarden.dev/netgarden/maf-webserver
|
||||
|
||||
go 1.24.1
|
||||
|
||||
replace netgarden.dev/netgarden/maf => ../maf
|
||||
|
||||
replace netgarden.dev/netgarden/mergefs => ../mergefs
|
||||
|
||||
require (
|
||||
github.com/flosch/pongo2/v6 v6.0.0
|
||||
github.com/labstack/echo/v4 v4.13.3
|
||||
netgarden.dev/netgarden/maf v0.0.0-00010101000000-000000000000
|
||||
netgarden.dev/netgarden/mergefs v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/elliotchance/orderedmap v1.6.0 // indirect
|
||||
github.com/kelseyhightower/envconfig v1.4.0 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.8.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
50
go.sum
Normal file
50
go.sum
Normal file
@ -0,0 +1,50 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/elliotchance/orderedmap v1.6.0 h1:xjn+kbbKXeDq6v9RVE+WYwRbYfAZKvlWfcJNxM8pvEw=
|
||||
github.com/elliotchance/orderedmap v1.6.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
|
||||
github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU=
|
||||
github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU=
|
||||
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
||||
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
|
||||
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
28
group.go
Normal file
28
group.go
Normal file
@ -0,0 +1,28 @@
|
||||
package webserver
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type echoGroup = echo.Group
|
||||
type Group struct {
|
||||
*echoGroup
|
||||
}
|
||||
|
||||
func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) *Group {
|
||||
return &Group{
|
||||
g.echoGroup.Group(prefix, middleware...),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Group) AddMany(methods string, path string, handler echo.HandlerFunc) {
|
||||
|
||||
for _, method := range strings.Split(methods, " ") {
|
||||
method = strings.TrimSpace(method)
|
||||
if method != "" {
|
||||
g.Add(method, path, handler)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
36
interfaces.go
Normal file
36
interfaces.go
Normal file
@ -0,0 +1,36 @@
|
||||
package webserver
|
||||
|
||||
import (
|
||||
"github.com/flosch/pongo2/v6"
|
||||
"io/fs"
|
||||
"netgarden.dev/netgarden/maf"
|
||||
)
|
||||
|
||||
type Controller interface {
|
||||
Register(app *Group)
|
||||
}
|
||||
|
||||
type WebModule interface {
|
||||
maf.Module
|
||||
GetWebControllers() []Controller
|
||||
}
|
||||
|
||||
type WebModuleStaticFSProvider interface {
|
||||
WebModule
|
||||
GetWebStaticFS() fs.FS
|
||||
}
|
||||
|
||||
type WebModuleTemplatesFSProvider interface {
|
||||
WebModule
|
||||
GetWebTemplatesFS() fs.FS
|
||||
}
|
||||
|
||||
type WebModuleTemplateTagsProvider interface {
|
||||
WebModule
|
||||
GetWebTemplateTags(renderer *Renderer) map[string]pongo2.TagParser
|
||||
}
|
||||
|
||||
type WebModuleTemplateFiltersProvider interface {
|
||||
WebModule
|
||||
GetWebTemplateFilters() map[string]pongo2.FilterFunction
|
||||
}
|
144
module.go
Normal file
144
module.go
Normal file
@ -0,0 +1,144 @@
|
||||
package webserver
|
||||
|
||||
import (
|
||||
"github.com/flosch/pongo2/v6"
|
||||
"io/fs"
|
||||
"netgarden.dev/netgarden/maf"
|
||||
"netgarden.dev/netgarden/mergefs"
|
||||
)
|
||||
|
||||
func NewModule(moduleConfig *ModuleConfig) *Module {
|
||||
return &Module{
|
||||
moduleConfig: moduleConfig,
|
||||
}
|
||||
}
|
||||
|
||||
type Module struct {
|
||||
moduleConfig *ModuleConfig
|
||||
manager *maf.Manager
|
||||
config *Config
|
||||
server *Server
|
||||
}
|
||||
|
||||
func (m *Module) GetID() string {
|
||||
return "webserver"
|
||||
}
|
||||
|
||||
func (m *Module) GetName() string {
|
||||
return "webserver"
|
||||
}
|
||||
|
||||
func (m *Module) SetManager(manager *maf.Manager) {
|
||||
m.manager = manager
|
||||
}
|
||||
|
||||
func (m *Module) CreateConfig() interface{} {
|
||||
return NewConfig()
|
||||
}
|
||||
|
||||
func (m *Module) SetConfig(cfg interface{}) {
|
||||
m.config = cfg.(*Config)
|
||||
}
|
||||
|
||||
func (m *Module) GetConfig() *Config {
|
||||
return m.config
|
||||
}
|
||||
|
||||
func (m *Module) Initialize() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) PreStart() error {
|
||||
|
||||
m.server = NewServer(m)
|
||||
|
||||
for _, module := range m.manager.GetModulesList() {
|
||||
|
||||
webModule, ok := module.(WebModule)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if tagsProvider, ok := module.(WebModuleTemplateTagsProvider); ok {
|
||||
for name, tag := range tagsProvider.GetWebTemplateTags(m.server.GetRenderer()) {
|
||||
err := pongo2.RegisterTag(name, tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if filtersProvider, ok := module.(WebModuleTemplateFiltersProvider); ok {
|
||||
for name, filter := range filtersProvider.GetWebTemplateFilters() {
|
||||
err := pongo2.RegisterFilter(name, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
routerGroup := m.server.GetGroup()
|
||||
controllers := webModule.GetWebControllers()
|
||||
for _, controller := range controllers {
|
||||
controller.Register(routerGroup)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) Start() error {
|
||||
return m.server.Start()
|
||||
}
|
||||
|
||||
func (m *Module) Stop() error {
|
||||
if m.server == nil {
|
||||
return nil
|
||||
}
|
||||
return m.server.Stop()
|
||||
}
|
||||
|
||||
func (m *Module) getStaticFS() fs.FS {
|
||||
|
||||
fsList := make([]fs.FS, 0)
|
||||
|
||||
for _, module := range m.manager.GetModulesList() {
|
||||
|
||||
webModule, ok := module.(WebModuleStaticFSProvider)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
tplFS := webModule.GetWebStaticFS()
|
||||
if tplFS == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fsList = append(fsList, tplFS)
|
||||
}
|
||||
|
||||
return mergefs.Merge(fsList...)
|
||||
}
|
||||
|
||||
func (m *Module) getTemplatesFS() fs.FS {
|
||||
|
||||
fsList := make([]fs.FS, 0)
|
||||
|
||||
for _, module := range m.manager.GetModulesList() {
|
||||
|
||||
webModule, ok := module.(WebModuleTemplatesFSProvider)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
tplFS := webModule.GetWebTemplatesFS()
|
||||
if tplFS == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fsList = append(fsList, tplFS)
|
||||
}
|
||||
|
||||
return mergefs.Merge(fsList...)
|
||||
}
|
7
module_config.go
Normal file
7
module_config.go
Normal file
@ -0,0 +1,7 @@
|
||||
package webserver
|
||||
|
||||
type MiddlewareFuncProvider = func() MiddlewareFunc
|
||||
|
||||
type ModuleConfig struct {
|
||||
Middlewares []MiddlewareFuncProvider
|
||||
}
|
89
renderer.go
Normal file
89
renderer.go
Normal file
@ -0,0 +1,89 @@
|
||||
package webserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/flosch/pongo2/v6"
|
||||
"github.com/labstack/echo/v4"
|
||||
"io"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
type Renderer struct {
|
||||
fs fs.FS
|
||||
templates *pongo2.TemplateSet
|
||||
}
|
||||
|
||||
// NewRenderer creates a new Renderer struct.
|
||||
func NewRenderer(fs fs.FS) *Renderer {
|
||||
|
||||
r := &Renderer{
|
||||
fs: fs,
|
||||
}
|
||||
|
||||
templates := pongo2.NewSet("templates", r)
|
||||
r.templates = templates
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Abs returns absolute path to file requested.
|
||||
// Search path is configured in AddDirectory method.
|
||||
// And default directory is "./templates".
|
||||
func (p *Renderer) Abs(base, name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
// Get reads the path's content from your local filesystem.
|
||||
func (p *Renderer) Get(path string) (io.Reader, error) {
|
||||
|
||||
f, err := p.fs.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bytes.NewReader(data), nil
|
||||
}
|
||||
|
||||
// RegisterTag registers a custom tag.
|
||||
// It calls pongo2.RegisterTag method.
|
||||
func (p *Renderer) RegisterTag(name string, parserFunc pongo2.TagParser) {
|
||||
pongo2.RegisterTag(name, parserFunc)
|
||||
}
|
||||
|
||||
// RegisterFilter registers a custom filter.
|
||||
// It calls pongo2.RegisterFilter method.
|
||||
func (p *Renderer) RegisterFilter(name string, fn pongo2.FilterFunction) {
|
||||
pongo2.RegisterFilter(name, fn)
|
||||
}
|
||||
|
||||
// SetDebug sets debug mode to the template set.
|
||||
// See pongo2.TemplateSet.Debug for more information.
|
||||
func (p *Renderer) SetDebug(v bool) {
|
||||
p.templates.Debug = v
|
||||
}
|
||||
|
||||
// Render renders the view.
|
||||
// Many other times, this is called in your echo handler functions.
|
||||
func (p *Renderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||
tmpl, err := p.templates.FromCache(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tmpl == nil {
|
||||
return fmt.Errorf("template '%s' not found", name)
|
||||
}
|
||||
d, ok := data.(map[string]interface{})
|
||||
if !ok {
|
||||
return errors.New("Incorrect data format. Should be map[string]interface{}")
|
||||
}
|
||||
|
||||
return tmpl.ExecuteWriter(d, w)
|
||||
}
|
115
server.go
Normal file
115
server.go
Normal file
@ -0,0 +1,115 @@
|
||||
package webserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
module *Module
|
||||
listener *net.Listener
|
||||
csrfConfig *middleware.CSRFConfig
|
||||
router *echo.Echo
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
func NewServer(module *Module) *Server {
|
||||
|
||||
s := &Server{
|
||||
module: module,
|
||||
}
|
||||
|
||||
s.init()
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Server) init() {
|
||||
|
||||
s.csrfConfig = &middleware.CSRFConfig{
|
||||
TokenLookup: "form:_csrf,query:csrf,header:X-CSRF-Token",
|
||||
}
|
||||
|
||||
s.router = echo.New()
|
||||
//s.router.Pre(middleware.RemoveTrailingSlash())
|
||||
s.router.Use(middleware.Logger())
|
||||
s.router.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{
|
||||
LogLevel: 4,
|
||||
}))
|
||||
s.router.Use(middleware.CSRFWithConfig(*s.csrfConfig))
|
||||
s.router.Use(CustomContext(s.csrfConfig))
|
||||
if s.module.moduleConfig != nil && s.module.moduleConfig.Middlewares != nil {
|
||||
for _, m := range s.module.moduleConfig.Middlewares {
|
||||
s.router.Use(m())
|
||||
}
|
||||
}
|
||||
|
||||
tplFS := s.module.getTemplatesFS()
|
||||
s.router.Renderer = NewRenderer(tplFS)
|
||||
|
||||
s.router.StaticFS("/static", s.module.getStaticFS())
|
||||
|
||||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
|
||||
config := &net.ListenConfig{Control: s.reusePort}
|
||||
listener, err := config.Listen(
|
||||
context.Background(),
|
||||
"tcp",
|
||||
fmt.Sprintf(":%d", s.module.config.Port),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.listener = &listener
|
||||
|
||||
s.server = &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", s.module.config.Port),
|
||||
Handler: s.router,
|
||||
}
|
||||
|
||||
go s.server.Serve(listener)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Stop() error {
|
||||
|
||||
if s.server == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
timeout := 10 * time.Second
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
return s.server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func (s *Server) GetGroup() *Group {
|
||||
return &Group{
|
||||
s.router.Group("/"),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) GetRenderer() *Renderer {
|
||||
return s.router.Renderer.(*Renderer)
|
||||
}
|
||||
|
||||
func (s *Server) reusePort(network, address string, conn syscall.RawConn) error {
|
||||
return conn.Control(func(descriptor uintptr) {
|
||||
err := syscall.SetsockoptInt(int(descriptor), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||
if err != nil {
|
||||
slog.Error("error during setting reuseport", err)
|
||||
}
|
||||
})
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user