diff options
| -rw-r--r-- | Makefile | 5 | ||||
| -rw-r--r-- | go.mod | 1 | ||||
| -rw-r--r-- | go.sum | 11 | ||||
| -rw-r--r-- | walrss/internal/core/users.go | 18 | ||||
| -rw-r--r-- | walrss/internal/http/auth.go | 100 | ||||
| -rw-r--r-- | walrss/internal/http/http.go | 24 | ||||
| -rw-r--r-- | walrss/internal/http/views/layoutComponents.qtpl.html | 9 | ||||
| -rw-r--r-- | walrss/internal/http/views/layoutComponents.qtpl.html.go | 43 | ||||
| -rw-r--r-- | walrss/internal/http/views/page.qtpl.html | 59 | ||||
| -rw-r--r-- | walrss/internal/http/views/page.qtpl.html.go | 237 | ||||
| -rw-r--r-- | walrss/internal/http/views/register.qtpl.html | 37 | ||||
| -rw-r--r-- | walrss/internal/http/views/register.qtpl.html.go | 96 | ||||
| -rw-r--r-- | walrss/internal/http/views/signin.qtpl.html | 33 | ||||
| -rw-r--r-- | walrss/internal/http/views/signin.qtpl.html.go | 92 | ||||
| -rw-r--r-- | walrss/internal/http/views/views.go | 19 |
15 files changed, 762 insertions, 22 deletions
@@ -1,6 +1,6 @@ .PHONY: prebuild fmt -build: +build: templates mkdir -p bin go build -o bin/walrss github.com/codemicro/walrss/walrss @@ -10,3 +10,6 @@ run: build fmt: go fmt github.com/codemicro/walrss/... + +templates: + qtc -skipLineComments -ext qtpl.html -dir walrss/internal/http/views
\ No newline at end of file @@ -16,6 +16,7 @@ require ( github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.34.0 // indirect + github.com/valyala/quicktemplate v1.7.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect go.etcd.io/bbolt v1.3.6 // indirect golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect @@ -1,3 +1,5 @@ +github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/bwmarrin/go-alone v0.0.0-20190806015146-742bb55d1631 h1:Xb5rra6jJt5Z1JsZhIMby+IP5T8aU+Uc2RC9RzSxs9g= @@ -6,10 +8,13 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofiber/fiber/v2 v2.31.0 h1:M2rWPQbD5fDVAjcoOLjKRXTIlHesI5Eq7I5FEQPt4Ow= github.com/gofiber/fiber/v2 v2.31.0/go.mod h1:1Ega6O199a3Y7yDGuM9FyXDPYQfv+7/y48wl6WCwUF4= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kkyr/fig v0.3.0 h1:5bd1amYKp/gsK2bGEUJYzcCrQPKOZp6HZD9K21v9Guo= github.com/kkyr/fig v0.3.0/go.mod h1:fEnrLjwg/iwSr8ksJF4DxrDmCUir5CaVMLORGYMcz30= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c= @@ -26,8 +31,11 @@ github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a h1:oIi7H/bwFUY github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a/go.mod h1:iSvujNDmpZ6eQX+bg/0X3lF7LEmZ8N77g2a/J/+Zt2U= 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/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4= github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= +github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM= +github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -35,6 +43,7 @@ go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -42,6 +51,7 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -52,6 +62,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/walrss/internal/core/users.go b/walrss/internal/core/users.go index dfc3635..2343de7 100644 --- a/walrss/internal/core/users.go +++ b/walrss/internal/core/users.go @@ -42,11 +42,8 @@ func RegisterUser(st *state.State, email, password string) (*db.User, error) { } func AreUserCredentialsCorrect(st *state.State, email, password string) (bool, error) { - user := new(db.User) - if err := st.Data.FindOne(user, bh.Where("Email").Eq(email)); err != nil { - if errors.Is(err, bh.ErrNotFound) { - return false, ErrNotFound - } + user, err := GetUserByEmail(st, email) + if err != nil { return false, err } @@ -59,3 +56,14 @@ func AreUserCredentialsCorrect(st *state.State, email, password string) (bool, e return true, nil } + +func GetUserByEmail(st *state.State, userID string) (*db.User, error) { + user := new(db.User) + if err := st.Data.FindOne(user, bh.Where("Email").Eq(userID)); err != nil { + if errors.Is(err, bh.ErrNotFound) { + return nil, ErrNotFound + } + return nil, err + } + return user, nil +} diff --git a/walrss/internal/http/auth.go b/walrss/internal/http/auth.go index 7be4078..eff478d 100644 --- a/walrss/internal/http/auth.go +++ b/walrss/internal/http/auth.go @@ -1,29 +1,99 @@ package http import ( + "errors" "github.com/codemicro/walrss/walrss/internal/core" + "github.com/codemicro/walrss/walrss/internal/http/views" + "github.com/codemicro/walrss/walrss/internal/urls" "github.com/gofiber/fiber/v2" "time" ) func (s *Server) authRegister(ctx *fiber.Ctx) error { - user, err := core.RegisterUser(s.state, - ctx.FormValue("email"), - ctx.FormValue("password"), - ) - if err != nil { - return err + page := new(views.RegisterPage) + + if ctx.Method() == fiber.MethodPost { + password := ctx.FormValue("password") + passwordConfirmation := ctx.FormValue("passwordConfirmation") + if password != passwordConfirmation { + page.Problem = "Passwords do not match" + goto exit + } + + user, err := core.RegisterUser( + s.state, + ctx.FormValue("email"), + password, + ) + if err != nil { + if core.IsUserError(err) { + ctx.Status(core.GetUserErrorStatus(err)) + page.Problem = "Could not register account: " + err.Error() + goto exit + } + return err + } + + token := core.GenerateSessionToken(user.ID) + + ctx.Cookie(&fiber.Cookie{ + Name: sessionCookieKey, + Value: token, + Expires: time.Now().UTC().Add(sessionDuration), + Secure: !s.state.Config.Debug, + HTTPOnly: true, + }) + + return ctx.Redirect(urls.Index) } - token := core.GenerateSessionToken(user.ID) +exit: + return views.SendPage(ctx, page) +} + +func (s *Server) authSignIn(ctx *fiber.Ctx) error { + page := &views.SignInPage{} + + if ctx.Method() == fiber.MethodPost { + email := ctx.FormValue("email") + + ok, err := core.AreUserCredentialsCorrect( + s.state, + email, + ctx.FormValue("password"), + ) + if err != nil { + if errors.Is(err, core.ErrNotFound) { + goto incorrectUsernameOrPassword + } + return err + } + + if !ok { + goto incorrectUsernameOrPassword + } + + user, err := core.GetUserByEmail(s.state, email) + if err != nil { + return err + } + + token := core.GenerateSessionToken(user.ID) + + ctx.Cookie(&fiber.Cookie{ + Name: sessionCookieKey, + Value: token, + Expires: time.Now().UTC().Add(sessionDuration), + Secure: !s.state.Config.Debug, + HTTPOnly: true, + }) + + return ctx.Redirect(urls.Index) + } - ctx.Cookie(&fiber.Cookie{ - Name: sessionCookieKey, - Value: token, - Expires: time.Now().UTC().Add(sessionDuration), - Secure: !s.state.Config.Debug, - HTTPOnly: true, - }) + return views.SendPage(ctx, page) - return ctx.SendString("ok!") +incorrectUsernameOrPassword: + ctx.Status(fiber.StatusUnauthorized) + return views.SendPage(ctx, &views.SignInPage{Problem: "Incorrect username or password"}) } diff --git a/walrss/internal/http/http.go b/walrss/internal/http/http.go index 5af260e..92829f7 100644 --- a/walrss/internal/http/http.go +++ b/walrss/internal/http/http.go @@ -5,6 +5,7 @@ import ( "github.com/codemicro/walrss/walrss/internal/state" "github.com/codemicro/walrss/walrss/internal/urls" "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog/log" "time" ) @@ -22,8 +23,25 @@ func New(st *state.State) (*Server, error) { app := fiber.New(fiber.Config{ DisableStartupMessage: !st.Config.Debug, AppName: "Walrss", + ErrorHandler: func(ctx *fiber.Ctx, err error) error { + code := fiber.StatusInternalServerError + msg := "Internal Server Error" + + switch e := err.(type) { + case *fiber.Error: + code = e.Code + msg = err.Error() + case *core.UserError: + code = e.Status + msg = err.Error() + default: + log.Error().Err(err).Str("location", "http").Str("url", ctx.OriginalURL()).Send() + } + + ctx.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) + return ctx.Status(code).SendString(msg) + }, }) - // TODO: Add error handler with UserError support s := &Server{ state: st, @@ -36,7 +54,11 @@ func New(st *state.State) (*Server, error) { } func (s *Server) registerHandlers() { + s.app.Get(urls.AuthRegister, s.authRegister) s.app.Post(urls.AuthRegister, s.authRegister) + + s.app.Get(urls.AuthSignIn, s.authSignIn) + s.app.Post(urls.AuthSignIn, s.authSignIn) } func (s *Server) Run() error { diff --git a/walrss/internal/http/views/layoutComponents.qtpl.html b/walrss/internal/http/views/layoutComponents.qtpl.html new file mode 100644 index 0000000..bc3ef1c --- /dev/null +++ b/walrss/internal/http/views/layoutComponents.qtpl.html @@ -0,0 +1,9 @@ +{% import "github.com/codemicro/walrss/walrss/internal/urls" %} + +{% func navbar() %} +<nav class="navbar navbar-light bg-light mb-3"> + <div class="container-fluid"> + <a class="navbar-brand" href="{%s= urls.Index %}">Walrss</a> + </div> +</nav> +{% endfunc %}
\ No newline at end of file diff --git a/walrss/internal/http/views/layoutComponents.qtpl.html.go b/walrss/internal/http/views/layoutComponents.qtpl.html.go new file mode 100644 index 0000000..28af874 --- /dev/null +++ b/walrss/internal/http/views/layoutComponents.qtpl.html.go @@ -0,0 +1,43 @@ +// Code generated by qtc from "layoutComponents.qtpl.html". DO NOT EDIT. +// See https://github.com/valyala/quicktemplate for details. + +package views + +import "github.com/codemicro/walrss/walrss/internal/urls" + +import ( + qtio422016 "io" + + qt422016 "github.com/valyala/quicktemplate" +) + +var ( + _ = qtio422016.Copy + _ = qt422016.AcquireByteBuffer +) + +func streamnavbar(qw422016 *qt422016.Writer) { + qw422016.N().S(` +<nav class="navbar navbar-light bg-light mb-3"> + <div class="container-fluid"> + <a class="navbar-brand" href="`) + qw422016.N().S(urls.Index) + qw422016.N().S(`">Walrss</a> + </div> +</nav> +`) +} + +func writenavbar(qq422016 qtio422016.Writer) { + qw422016 := qt422016.AcquireWriter(qq422016) + streamnavbar(qw422016) + qt422016.ReleaseWriter(qw422016) +} + +func navbar() string { + qb422016 := qt422016.AcquireByteBuffer() + writenavbar(qb422016) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} diff --git a/walrss/internal/http/views/page.qtpl.html b/walrss/internal/http/views/page.qtpl.html new file mode 100644 index 0000000..968ea7d --- /dev/null +++ b/walrss/internal/http/views/page.qtpl.html @@ -0,0 +1,59 @@ +{% interface +Page { + Title() + Body() + HeadContent() +} +%} + +Page prints a page implementing Page interface. +{% func RenderPage(p Page) %} +<!DOCTYPE html> +<html> +<head> + <title>{%s= makePageTitle(p) %}</title> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> + {%= p.HeadContent() %} +</head> +<body> +{%= p.Body() %} +</body> +</html> +{% endfunc %} + +{% code type BasePage struct {} %} +{% func (p *BasePage) Title() %}{% endfunc %} +{% func (p *BasePage) HeadContent() %}{% endfunc %} + +{% func ProblemBox(p string) %} +<div class="alert alert-danger" role="alert"> + {%s= p %} +</div> +{% endfunc %} + +{% func SuccessBox(p string) %} +<div class="alert alert-success" role="alert"> + {%s= p %} +</div> +{% endfunc %} + +{% func WarningBox(p string) %} +<div class="alert alert-warning" role="alert"> + {%s= p %} +</div> +{% endfunc %} + +PolyPage is used to create a basic page dynamically using Daz. +{% code +type PolyPage struct { + BasePage + TitleString string + BodyContent string + ExtraHeadContent string +} +%} + +{% func (p *PolyPage) Title() %}{%s= p.TitleString %}{% endfunc %} +{% func (p *PolyPage) Body() %}{%s= p.BodyContent %}{% endfunc %} +{% func (p *PolyPage) HeadContent() %}{%s= p.ExtraHeadContent %}{% endfunc %}
\ No newline at end of file diff --git a/walrss/internal/http/views/page.qtpl.html.go b/walrss/internal/http/views/page.qtpl.html.go new file mode 100644 index 0000000..125a3b0 --- /dev/null +++ b/walrss/internal/http/views/page.qtpl.html.go @@ -0,0 +1,237 @@ +// Code generated by qtc from "page.qtpl.html". DO NOT EDIT. +// See https://github.com/valyala/quicktemplate for details. + +package views + +import ( + qtio422016 "io" + + qt422016 "github.com/valyala/quicktemplate" +) + +var ( + _ = qtio422016.Copy + _ = qt422016.AcquireByteBuffer +) + +type Page interface { + Title() string + StreamTitle(qw422016 *qt422016.Writer) + WriteTitle(qq422016 qtio422016.Writer) + Body() string + StreamBody(qw422016 *qt422016.Writer) + WriteBody(qq422016 qtio422016.Writer) + HeadContent() string + StreamHeadContent(qw422016 *qt422016.Writer) + WriteHeadContent(qq422016 qtio422016.Writer) +} + +// Page prints a page implementing Page interface. + +func StreamRenderPage(qw422016 *qt422016.Writer, p Page) { + qw422016.N().S(` +<!DOCTYPE html> +<html> +<head> + <title>`) + qw422016.N().S(makePageTitle(p)) + qw422016.N().S(`</title> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> + `) + p.StreamHeadContent(qw422016) + qw422016.N().S(` +</head> +<body> +`) + p.StreamBody(qw422016) + qw422016.N().S(` +</body> +</html> +`) +} + +func WriteRenderPage(qq422016 qtio422016.Writer, p Page) { + qw422016 := qt422016.AcquireWriter(qq422016) + StreamRenderPage(qw422016, p) + qt422016.ReleaseWriter(qw422016) +} + +func RenderPage(p Page) string { + qb422016 := qt422016.AcquireByteBuffer() + WriteRenderPage(qb422016, p) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} + +type BasePage struct{} + +func (p *BasePage) StreamTitle(qw422016 *qt422016.Writer) { +} + +func (p *BasePage) WriteTitle(qq422016 qtio422016.Writer) { + qw422016 := qt422016.AcquireWriter(qq422016) + p.StreamTitle(qw422016) + qt422016.ReleaseWriter(qw422016) +} + +func (p *BasePage) Title() string { + qb422016 := qt422016.AcquireByteBuffer() + p.WriteTitle(qb422016) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} + +func (p *BasePage) StreamHeadContent(qw422016 *qt422016.Writer) { +} + +func (p *BasePage) WriteHeadContent(qq422016 qtio422016.Writer) { + qw422016 := qt422016.AcquireWriter(qq422016) + p.StreamHeadContent(qw422016) + qt422016.ReleaseWriter(qw422016) +} + +func (p *BasePage) HeadContent() string { + qb422016 := qt422016.AcquireByteBuffer() + p.WriteHeadContent(qb422016) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} + +func StreamProblemBox(qw422016 *qt422016.Writer, p string) { + qw422016.N().S(` +<div class="alert alert-danger" role="alert"> + `) + qw422016.N().S(p) + qw422016.N().S(` +</div> +`) +} + +func WriteProblemBox(qq422016 qtio422016.Writer, p string) { + qw422016 := qt422016.AcquireWriter(qq422016) + StreamProblemBox(qw422016, p) + qt422016.ReleaseWriter(qw422016) +} + +func ProblemBox(p string) string { + qb422016 := qt422016.AcquireByteBuffer() + WriteProblemBox(qb422016, p) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} + +func StreamSuccessBox(qw422016 *qt422016.Writer, p string) { + qw422016.N().S(` +<div class="alert alert-success" role="alert"> + `) + qw422016.N().S(p) + qw422016.N().S(` +</div> +`) +} + +func WriteSuccessBox(qq422016 qtio422016.Writer, p string) { + qw422016 := qt422016.AcquireWriter(qq422016) + StreamSuccessBox(qw422016, p) + qt422016.ReleaseWriter(qw422016) +} + +func SuccessBox(p string) string { + qb422016 := qt422016.AcquireByteBuffer() + WriteSuccessBox(qb422016, p) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} + +func StreamWarningBox(qw422016 *qt422016.Writer, p string) { + qw422016.N().S(` +<div class="alert alert-warning" role="alert"> + `) + qw422016.N().S(p) + qw422016.N().S(` +</div> +`) +} + +func WriteWarningBox(qq422016 qtio422016.Writer, p string) { + qw422016 := qt422016.AcquireWriter(qq422016) + StreamWarningBox(qw422016, p) + qt422016.ReleaseWriter(qw422016) +} + +func WarningBox(p string) string { + qb422016 := qt422016.AcquireByteBuffer() + WriteWarningBox(qb422016, p) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} + +// PolyPage is used to create a basic page dynamically using Daz. + +type PolyPage struct { + BasePage + TitleString string + BodyContent string + ExtraHeadContent string +} + +func (p *PolyPage) StreamTitle(qw422016 *qt422016.Writer) { + qw422016.N().S(p.TitleString) +} + +func (p *PolyPage) WriteTitle(qq422016 qtio422016.Writer) { + qw422016 := qt422016.AcquireWriter(qq422016) + p.StreamTitle(qw422016) + qt422016.ReleaseWriter(qw422016) +} + +func (p *PolyPage) Title() string { + qb422016 := qt422016.AcquireByteBuffer() + p.WriteTitle(qb422016) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} + +func (p *PolyPage) StreamBody(qw422016 *qt422016.Writer) { + qw422016.N().S(p.BodyContent) +} + +func (p *PolyPage) WriteBody(qq422016 qtio422016.Writer) { + qw422016 := qt422016.AcquireWriter(qq422016) + p.StreamBody(qw422016) + qt422016.ReleaseWriter(qw422016) +} + +func (p *PolyPage) Body() string { + qb422016 := qt422016.AcquireByteBuffer() + p.WriteBody(qb422016) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} + +func (p *PolyPage) StreamHeadContent(qw422016 *qt422016.Writer) { + qw422016.N().S(p.ExtraHeadContent) +} + +func (p *PolyPage) WriteHeadContent(qq422016 qtio422016.Writer) { + qw422016 := qt422016.AcquireWriter(qq422016) + p.StreamHeadContent(qw422016) + qt422016.ReleaseWriter(qw422016) +} + +func (p *PolyPage) HeadContent() string { + qb422016 := qt422016.AcquireByteBuffer() + p.WriteHeadContent(qb422016) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} diff --git a/walrss/internal/http/views/register.qtpl.html b/walrss/internal/http/views/register.qtpl.html new file mode 100644 index 0000000..812a671 --- /dev/null +++ b/walrss/internal/http/views/register.qtpl.html @@ -0,0 +1,37 @@ +{% import "github.com/codemicro/walrss/walrss/internal/urls" %} + +{% code type RegisterPage struct { + BasePage + Problem string +} %} + +{% func (p *RegisterPage) Title() %}Register{% endfunc %} +{% func (p *RegisterPage) Body() %} +{%= navbar() %} + +<div class="container"> + <h1>Register</h1> + + {% if p.Problem != "" %} + {%= ProblemBox(p.Problem) %} + {% endif %} + + <form action="" method="post"> + <div class="mb-3"> + <label for="emailInput" class="form-label">Email address</label> + <input type="email" class="form-control" id="emailInput" name="email"> + </div> + <div class="mb-3"> + <label for="passwordInput" class="form-label">Password</label> + <input type="password" class="form-control" id="passwordInput" name="password"> + </div> + <div class="mb-3"> + <label for="passwordConfirmation" class="form-label">Retype password</label> + <input type="password" class="form-control" id="passwordConfirmation" name="passwordConfirmation"> + </div> + <button type="submit" class="btn btn-primary">Submit</button> + </form> + <br> + <a href="{%s= urls.AuthSignIn %}">Already got an account? Click here to sign in</a> +</div> +{% endfunc %}
\ No newline at end of file diff --git a/walrss/internal/http/views/register.qtpl.html.go b/walrss/internal/http/views/register.qtpl.html.go new file mode 100644 index 0000000..d25fdfb --- /dev/null +++ b/walrss/internal/http/views/register.qtpl.html.go @@ -0,0 +1,96 @@ +// Code generated by qtc from "register.qtpl.html". DO NOT EDIT. +// See https://github.com/valyala/quicktemplate for details. + +package views + +import "github.com/codemicro/walrss/walrss/internal/urls" + +import ( + qtio422016 "io" + + qt422016 "github.com/valyala/quicktemplate" +) + +var ( + _ = qtio422016.Copy + _ = qt422016.AcquireByteBuffer +) + +type RegisterPage struct { + BasePage + Problem string +} + +func (p *RegisterPage) StreamTitle(qw422016 *qt422016.Writer) { + qw422016.N().S(`Register`) +} + +func (p *RegisterPage) WriteTitle(qq422016 qtio422016.Writer) { + qw422016 := qt422016.AcquireWriter(qq422016) + p.StreamTitle(qw422016) + qt422016.ReleaseWriter(qw422016) +} + +func (p *RegisterPage) Title() string { + qb422016 := qt422016.AcquireByteBuffer() + p.WriteTitle(qb422016) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} + +func (p *RegisterPage) StreamBody(qw422016 *qt422016.Writer) { + qw422016.N().S(` +`) + streamnavbar(qw422016) + qw422016.N().S(` + +<div class="container"> + <h1>Register</h1> + + `) + if p.Problem != "" { + qw422016.N().S(` + `) + StreamProblemBox(qw422016, p.Problem) + qw422016.N().S(` + `) + } + qw422016.N().S(` + + <form action="" method="post"> + <div class="mb-3"> + <label for="emailInput" class="form-label">Email address</label> + <input type="email" class="form-control" id="emailInput" name="email"> + </div> + <div class="mb-3"> + <label for="passwordInput" class="form-label">Password</label> + <input type="password" class="form-control" id="passwordInput" name="password"> + </div> + <div class="mb-3"> + <label for="passwordConfirmation" class="form-label">Retype password</label> + <input type="password" class="form-control" id="passwordConfirmation" name="passwordConfirmation"> + </div> + <button type="submit" class="btn btn-primary">Submit</button> + </form> + <br> + <a href="`) + qw422016.N().S(urls.AuthSignIn) + qw422016.N().S(`">Already got an account? Click here to sign in</a> +</div> +`) +} + +func (p *RegisterPage) WriteBody(qq422016 qtio422016.Writer) { + qw422016 := qt422016.AcquireWriter(qq422016) + p.StreamBody(qw422016) + qt422016.ReleaseWriter(qw422016) +} + +func (p *RegisterPage) Body() string { + qb422016 := qt422016.AcquireByteBuffer() + p.WriteBody(qb422016) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} diff --git a/walrss/internal/http/views/signin.qtpl.html b/walrss/internal/http/views/signin.qtpl.html new file mode 100644 index 0000000..3022f7f --- /dev/null +++ b/walrss/internal/http/views/signin.qtpl.html @@ -0,0 +1,33 @@ +{% import "github.com/codemicro/walrss/walrss/internal/urls" %} + +{% code type SignInPage struct { + BasePage + Problem string +} %} + +{% func (p *SignInPage) Title() %}Sign in{% endfunc %} +{% func (p *SignInPage) Body() %} +{%= navbar() %} + +<div class="container"> + <h1>Sign in</h1> + + {% if p.Problem != "" %} + {%= ProblemBox(p.Problem) %} + {% endif %} + + <form action="" method="post"> + <div class="mb-3"> + <label for="emailInput" class="form-label">Email address</label> + <input type="email" class="form-control" id="emailInput" name="email"> + </div> + <div class="mb-3"> + <label for="passwordInput" class="form-label">Password</label> + <input type="password" class="form-control" id="passwordInput" name="password"> + </div> + <button type="submit" class="btn btn-primary">Submit</button> + </form> + <br> + <a href="{%s= urls.AuthRegister %}">No account? Click here to register</a> +</div> +{% endfunc %}
\ No newline at end of file diff --git a/walrss/internal/http/views/signin.qtpl.html.go b/walrss/internal/http/views/signin.qtpl.html.go new file mode 100644 index 0000000..ba433dd --- /dev/null +++ b/walrss/internal/http/views/signin.qtpl.html.go @@ -0,0 +1,92 @@ +// Code generated by qtc from "signin.qtpl.html". DO NOT EDIT. +// See https://github.com/valyala/quicktemplate for details. + +package views + +import "github.com/codemicro/walrss/walrss/internal/urls" + +import ( + qtio422016 "io" + + qt422016 "github.com/valyala/quicktemplate" +) + +var ( + _ = qtio422016.Copy + _ = qt422016.AcquireByteBuffer +) + +type SignInPage struct { + BasePage + Problem string +} + +func (p *SignInPage) StreamTitle(qw422016 *qt422016.Writer) { + qw422016.N().S(`Sign in`) +} + +func (p *SignInPage) WriteTitle(qq422016 qtio422016.Writer) { + qw422016 := qt422016.AcquireWriter(qq422016) + p.StreamTitle(qw422016) + qt422016.ReleaseWriter(qw422016) +} + +func (p *SignInPage) Title() string { + qb422016 := qt422016.AcquireByteBuffer() + p.WriteTitle(qb422016) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} + +func (p *SignInPage) StreamBody(qw422016 *qt422016.Writer) { + qw422016.N().S(` +`) + streamnavbar(qw422016) + qw422016.N().S(` + +<div class="container"> + <h1>Sign in</h1> + + `) + if p.Problem != "" { + qw422016.N().S(` + `) + StreamProblemBox(qw422016, p.Problem) + qw422016.N().S(` + `) + } + qw422016.N().S(` + + <form action="" method="post"> + <div class="mb-3"> + <label for="emailInput" class="form-label">Email address</label> + <input type="email" class="form-control" id="emailInput" name="email"> + </div> + <div class="mb-3"> + <label for="passwordInput" class="form-label">Password</label> + <input type="password" class="form-control" id="passwordInput" name="password"> + </div> + <button type="submit" class="btn btn-primary">Submit</button> + </form> + <br> + <a href="`) + qw422016.N().S(urls.AuthRegister) + qw422016.N().S(`">No account? Click here to register</a> +</div> +`) +} + +func (p *SignInPage) WriteBody(qq422016 qtio422016.Writer) { + qw422016 := qt422016.AcquireWriter(qq422016) + p.StreamBody(qw422016) + qt422016.ReleaseWriter(qw422016) +} + +func (p *SignInPage) Body() string { + qb422016 := qt422016.AcquireByteBuffer() + p.WriteBody(qb422016) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} diff --git a/walrss/internal/http/views/views.go b/walrss/internal/http/views/views.go new file mode 100644 index 0000000..2c11f09 --- /dev/null +++ b/walrss/internal/http/views/views.go @@ -0,0 +1,19 @@ +package views + +import "github.com/gofiber/fiber/v2" + +//go:generate go install github.com/valyala/quicktemplate/qtc@latest +//go:generate qtc -skipLineComments -ext qtpl.html + +func SendPage(ctx *fiber.Ctx, page Page) error { + ctx.Set(fiber.HeaderContentType, "html") + return ctx.SendString(RenderPage(page)) +} + +func makePageTitle(p Page) string { + t := p.Title() + if t == "" { + return "Walrss" + } + return t + " | Walrss" +} |
