From 32ba36903f66d7613534f49df81fd834ba1c5925 Mon Sep 17 00:00:00 2001 From: AKP Date: Sat, 2 Apr 2022 21:10:52 +0100 Subject: Implement schedule updates Signed-off-by: AKP --- go.mod | 1 + go.sum | 2 + walrss/internal/core/sessions.go | 5 +- walrss/internal/core/users.go | 25 ++- walrss/internal/db/db.go | 29 +-- walrss/internal/db/sendDay.go | 80 +++++++ walrss/internal/http/auth.go | 8 +- walrss/internal/http/edit.go | 74 +++++++ walrss/internal/http/http.go | 37 ++++ walrss/internal/http/mainpage.go | 25 +++ .../internal/http/views/layoutComponents.qtpl.html | 2 +- .../http/views/layoutComponents.qtpl.html.go | 2 +- walrss/internal/http/views/main.qtpl.html | 137 ++++++++++++ walrss/internal/http/views/main.qtpl.html.go | 235 +++++++++++++++++++++ walrss/internal/http/views/page.qtpl.html | 17 +- walrss/internal/http/views/page.qtpl.html.go | 18 ++ walrss/internal/urls/urls.go | 6 +- 17 files changed, 675 insertions(+), 28 deletions(-) create mode 100644 walrss/internal/db/sendDay.go create mode 100644 walrss/internal/http/edit.go create mode 100644 walrss/internal/http/mainpage.go create mode 100644 walrss/internal/http/views/main.qtpl.html create mode 100644 walrss/internal/http/views/main.qtpl.html.go diff --git a/go.mod b/go.mod index bc60677..6a3149e 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/pelletier/go-toml v1.9.3 // indirect github.com/rs/zerolog v1.26.1 // indirect + github.com/stevelacy/daz v0.1.4 // indirect 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 diff --git a/go.sum b/go.sum index 77abf86..9faeebd 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= +github.com/stevelacy/daz v0.1.4 h1:ugmff/D7D764wZjXSgSryEINE/bi+Xddllw3JQQGbWk= +github.com/stevelacy/daz v0.1.4/go.mod h1:AbK6DzjiIL15r4bQtcFvOBAvDGMXoh+uIG26NRUugt0= github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a h1:oIi7H/bwFUYKYhzKbHc+3MvHRWqhQwXVB4LweLMiVy0= 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= diff --git a/walrss/internal/core/sessions.go b/walrss/internal/core/sessions.go index 8e1ab73..6beb13b 100644 --- a/walrss/internal/core/sessions.go +++ b/walrss/internal/core/sessions.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "encoding/hex" goalone "github.com/bwmarrin/go-alone" + "strings" "time" ) @@ -21,7 +22,7 @@ func init() { } func GenerateSessionToken(userID string) string { - combined := combineStringAndSalt(userID, sessionSalt) + combined := append([]byte(userID), sessionSalt...) return hex.EncodeToString(sessionSigner.Sign(combined)) } @@ -36,5 +37,5 @@ func ValidateSessionToken(input string) (string, time.Time, error) { } parsed := sessionSigner.Parse(signed) - return string(parsed.Payload), parsed.Timestamp, nil + return strings.TrimSuffix(string(parsed.Payload), string(sessionSalt)), parsed.Timestamp, nil } diff --git a/walrss/internal/core/users.go b/walrss/internal/core/users.go index 2343de7..e10da99 100644 --- a/walrss/internal/core/users.go +++ b/walrss/internal/core/users.go @@ -57,9 +57,9 @@ func AreUserCredentialsCorrect(st *state.State, email, password string) (bool, e return true, nil } -func GetUserByEmail(st *state.State, userID string) (*db.User, error) { +func GetUserByID(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 err := st.Data.FindOne(user, bh.Where("ID").Eq(userID)); err != nil { if errors.Is(err, bh.ErrNotFound) { return nil, ErrNotFound } @@ -67,3 +67,24 @@ func GetUserByEmail(st *state.State, userID string) (*db.User, error) { } return user, nil } + +func GetUserByEmail(st *state.State, email string) (*db.User, 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 nil, ErrNotFound + } + return nil, err + } + return user, nil +} + +func UpdateUser(st *state.State, user *db.User) error { + if err := st.Data.Update(user.ID, user); err != nil { + if errors.Is(err, bh.ErrNotFound) { + return ErrNotFound + } + return err + } + return nil +} diff --git a/walrss/internal/db/db.go b/walrss/internal/db/db.go index d9769e7..eb8590a 100644 --- a/walrss/internal/db/db.go +++ b/walrss/internal/db/db.go @@ -1,9 +1,15 @@ package db -import bh "github.com/timshannon/bolthold" +import ( + "encoding/json" + bh "github.com/timshannon/bolthold" +) func New(filename string) (*bh.Store, error) { - store, err := bh.Open(filename, 0644, nil) + store, err := bh.Open(filename, 0644, &bh.Options{ + Encoder: json.Marshal, + Decoder: json.Unmarshal, + }) if err != nil { return nil, err } @@ -17,21 +23,8 @@ type User struct { Salt []byte Schedule struct { - Day SendDay `boltholdIndex:"Day"` - Hour int `boltholdIndex:"Hour"` + Active bool `boltholdIndex:"Active"` + Day SendDay `boltholdIndex:"Day"` + Hour int `boltholdIndex:"Hour"` } } - -type SendDay uint32 - -const ( - SendDayNever = iota - SendDaily - SendOnMonday - SendOnTuesday - SendOnWednesday - SendOnThursday - SendOnFriday - SendOnSaturday - SendOnSunday -) diff --git a/walrss/internal/db/sendDay.go b/walrss/internal/db/sendDay.go new file mode 100644 index 0000000..5146a56 --- /dev/null +++ b/walrss/internal/db/sendDay.go @@ -0,0 +1,80 @@ +package db + +import ( + "errors" + "strings" +) + +type SendDay uint32 + +const ( + SendDayNever SendDay = iota + SendDaily + SendOnMonday + SendOnTuesday + SendOnWednesday + SendOnThursday + SendOnFriday + SendOnSaturday + SendOnSunday + LastSendDay +) + +func (s SendDay) String() string { + var x string + + switch s { + case SendDayNever: + x = "never" + case SendDaily: + x = "daily" + case SendOnMonday: + x = "Monday" + case SendOnTuesday: + x = "Tuesday" + case SendOnWednesday: + x = "Wednesday" + case SendOnThursday: + x = "Thursday" + case SendOnFriday: + x = "Friday" + case SendOnSaturday: + x = "Saturday" + case SendOnSunday: + x = "Sunday" + } + + return x +} + +func (s SendDay) MarshalText() ([]byte, error) { + return []byte(s.String()), nil +} + +func (s *SendDay) UnmarshalText(x []byte) error { + + switch strings.ToLower(string(x)) { + case "never": + *s = SendDayNever + case "daily": + *s = SendDaily + case "monday": + *s = SendOnMonday + case "tuesday": + *s = SendOnTuesday + case "wednesday": + *s = SendOnWednesday + case "thursday": + *s = SendOnThursday + case "friday": + *s = SendOnFriday + case "saturday": + *s = SendOnSaturday + case "sunday": + *s = SendOnSunday + default: + return errors.New("unrecognised day") + } + + return nil +} diff --git a/walrss/internal/http/auth.go b/walrss/internal/http/auth.go index 62e4295..ae917e6 100644 --- a/walrss/internal/http/auth.go +++ b/walrss/internal/http/auth.go @@ -58,7 +58,9 @@ success: } func (s *Server) authSignIn(ctx *fiber.Ctx) error { - page := &views.SignInPage{} + page := &views.SignInPage{ + Problem: ctx.Query("problem"), + } if getCurrentUserID(ctx) != "" { goto success @@ -103,7 +105,9 @@ func (s *Server) authSignIn(ctx *fiber.Ctx) error { return views.SendPage(ctx, page) success: - return ctx.Redirect(urls.Index) + return ctx.Redirect( + ctx.Query("next", urls.Index), + ) incorrectUsernameOrPassword: ctx.Status(fiber.StatusUnauthorized) return views.SendPage(ctx, &views.SignInPage{Problem: "Incorrect username or password"}) diff --git a/walrss/internal/http/edit.go b/walrss/internal/http/edit.go new file mode 100644 index 0000000..8599f07 --- /dev/null +++ b/walrss/internal/http/edit.go @@ -0,0 +1,74 @@ +package http + +import ( + "github.com/codemicro/walrss/walrss/internal/core" + "github.com/codemicro/walrss/walrss/internal/db" + "github.com/codemicro/walrss/walrss/internal/urls" + "github.com/gofiber/fiber/v2" + "strconv" + "strings" +) + +func (s *Server) editEnabledState(ctx *fiber.Ctx) error { + currentUserID := getCurrentUserID(ctx) + if currentUserID == "" { + return requestStandardSignIn(ctx) + } + + user, err := core.GetUserByID(s.state, currentUserID) + if err != nil { + return err + } + + if strings.ToLower(ctx.FormValue("enable", "off")) == "on" { + user.Schedule.Active = true + } else { + user.Schedule.Active = false + } + + if err := core.UpdateUser(s.state, user); err != nil { + return err + } + + ctx.Set("HX-Redirect", urls.Index) + return nil +} + +func (s *Server) editTimings(ctx *fiber.Ctx) error { + currentUserID := getCurrentUserID(ctx) + if currentUserID == "" { + return requestStandardSignIn(ctx) + } + + user, err := core.GetUserByID(s.state, currentUserID) + if err != nil { + return err + } + + if n, err := strconv.ParseInt(ctx.FormValue("day"), 10, 32); err != nil { + return core.AsUserError(fiber.StatusBadRequest, err) + } else { + x := db.SendDay(n) + if x > db.SendOnSunday || x < 0 { + return core.NewUserError("invalid day: out of range 0<=x<=%d", int(db.SendOnSunday)) + } + user.Schedule.Day = x + } + + if n, err := strconv.ParseInt(ctx.FormValue("time"), 10, 8); err != nil { + return core.AsUserError(fiber.StatusBadRequest, err) + } else { + x := int(n) + if x > 23 || x < 0 { + return core.NewUserError("invalid time: out of range 0<=x<=23") + } + user.Schedule.Hour = x + } + + if err := core.UpdateUser(s.state, user); err != nil { + return err + } + + ctx.Set("HX-Redirect", urls.Index) + return nil +} diff --git a/walrss/internal/http/http.go b/walrss/internal/http/http.go index 037e310..cf0589e 100644 --- a/walrss/internal/http/http.go +++ b/walrss/internal/http/http.go @@ -2,10 +2,13 @@ package http import ( "github.com/codemicro/walrss/walrss/internal/core" + "github.com/codemicro/walrss/walrss/internal/http/views" "github.com/codemicro/walrss/walrss/internal/state" "github.com/codemicro/walrss/walrss/internal/urls" "github.com/gofiber/fiber/v2" "github.com/rs/zerolog/log" + "github.com/stevelacy/daz" + "net/url" "time" ) @@ -68,11 +71,16 @@ func (s *Server) registerHandlers() { return ctx.Next() }) + s.app.Get(urls.Index, s.mainPage) + 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) + + s.app.Put(urls.EditEnabledState, s.editEnabledState) + s.app.Put(urls.EditTimings, s.editTimings) } func (s *Server) Run() error { @@ -93,3 +101,32 @@ func getCurrentUserID(ctx *fiber.Ctx) string { } return "" } + +func requestStandardSignIn(ctx *fiber.Ctx) error { + rdu := ctx.OriginalURL() // TODO: Could this use of OriginalURL be insecure? + + queryParams := make(url.Values) + queryParams.Add("problem", "Please sign in first.") + queryParams.Add("next", rdu) + nextURL := urls.AuthSignIn + "?" + queryParams.Encode() + + ctx.Status(fiber.StatusUnauthorized) + + // Instead of plainly redirecting, we use a HTML redirect here. This is to clear the HTTP verb used for this + // request. For example - if the request was made with DELETE, using ctx.Redirect will preserve that verb. Using + // this method will restart with a GET verb. + return views.SendPage(ctx, &views.PolyPage{ + TitleString: "Please sign in first", + BodyContent: daz.H("p", "Please sign in first. If your browser doesn't automatically redirect you, click ", daz.H("a", daz.Attr{"href": nextURL}, "here"), ".")(), + ExtraHeadContent: daz.H("meta", daz.Attr{"http-equiv": "Refresh", "content": "0; " + nextURL})(), + }) +} + +func requestFragmentSignIn(ctx *fiber.Ctx, nextURL string) error { + queryParams := make(url.Values) + queryParams.Add("problem", "Please sign in first.") + queryParams.Add("next", nextURL) + + ctx.Set("HX-Redirect", urls.AuthSignIn+"?"+queryParams.Encode()) + return nil +} diff --git a/walrss/internal/http/mainpage.go b/walrss/internal/http/mainpage.go new file mode 100644 index 0000000..6c60753 --- /dev/null +++ b/walrss/internal/http/mainpage.go @@ -0,0 +1,25 @@ +package http + +import ( + "github.com/codemicro/walrss/walrss/internal/core" + "github.com/codemicro/walrss/walrss/internal/http/views" + "github.com/gofiber/fiber/v2" +) + +func (s *Server) mainPage(ctx *fiber.Ctx) error { + currentUserID := getCurrentUserID(ctx) + if currentUserID == "" { + return requestStandardSignIn(ctx) + } + + user, err := core.GetUserByID(s.state, currentUserID) + if err != nil { + return err + } + + return views.SendPage(ctx, &views.MainPage{ + EnableDigests: user.Schedule.Active, + SelectedDay: user.Schedule.Day, + SelectedTime: user.Schedule.Hour, + }) +} diff --git a/walrss/internal/http/views/layoutComponents.qtpl.html b/walrss/internal/http/views/layoutComponents.qtpl.html index bc3ef1c..4643094 100644 --- a/walrss/internal/http/views/layoutComponents.qtpl.html +++ b/walrss/internal/http/views/layoutComponents.qtpl.html @@ -1,7 +1,7 @@ {% import "github.com/codemicro/walrss/walrss/internal/urls" %} {% func navbar() %} -