From dfa2525714ea6975f74eaf23aa0a73c53958b1a6 Mon Sep 17 00:00:00 2001 From: Teajey <21069848+Teajey@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:04:33 +0900 Subject: [PATCH] Use RSVP 0.11.0 --- http/go.mod | 19 ++--- http/go.sum | 31 +------- http/response/handler.go | 37 +++++----- http/response/request.go | 38 +--------- http/response/response.go | 117 ++++--------------------------- http/response/session.go | 8 ++- http/routing/context.go | 16 +++-- http/routing/error.go | 17 ----- http/routing/foreign_wishlist.go | 21 ++++-- http/routing/groups.go | 37 ++++++---- http/routing/home.go | 24 +++++-- http/routing/login.go | 36 ++++++---- http/routing/logout.go | 8 +-- http/routing/not_found.go | 4 +- http/routing/register.go | 32 +++++---- http/routing/todo.go | 27 ++++--- http/routing/users.go | 21 +++--- http/routing/wishlist.go | 76 +++++++++++++------- http/templates/templates.go | 15 ++-- 19 files changed, 244 insertions(+), 340 deletions(-) delete mode 100644 http/routing/error.go diff --git a/http/go.mod b/http/go.mod index ca1fb6a..c1584a2 100644 --- a/http/go.mod +++ b/http/go.mod @@ -1,25 +1,14 @@ module lishwist/http -go 1.23 +go 1.23.3 -toolchain go1.23.3 +toolchain go1.24.5 require ( + github.com/Teajey/rsvp v0.11.0 github.com/Teajey/sqlstore v0.0.6 - github.com/glebarez/go-sqlite v1.22.0 - github.com/google/uuid v1.6.0 github.com/gorilla/sessions v1.4.0 golang.org/x/crypto v0.22.0 ) -require ( - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/gorilla/securecookie v1.1.2 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - golang.org/x/sys v0.19.0 // indirect - modernc.org/libc v1.37.6 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.2 // indirect - modernc.org/sqlite v1.28.0 // indirect -) +require github.com/gorilla/securecookie v1.1.2 // indirect diff --git a/http/go.sum b/http/go.sum index 8b650a2..caa1377 100644 --- a/http/go.sum +++ b/http/go.sum @@ -1,39 +1,12 @@ -github.com/Teajey/sqlstore v0.0.3 h1:6Y1jz9/yw1cj/Z/jrii0s87RAomKWr/07B1auDgw8pg= -github.com/Teajey/sqlstore v0.0.3/go.mod h1:hjk0S593/2Q4QxkEXCgpThj9w5KWGTQi9JtgfziHXXk= -github.com/Teajey/sqlstore v0.0.4 h1:ATe25BD8cV0FUw4w2qlccx5m0c5kQI0K4ksl/LnSHsc= -github.com/Teajey/sqlstore v0.0.4/go.mod h1:hjk0S593/2Q4QxkEXCgpThj9w5KWGTQi9JtgfziHXXk= -github.com/Teajey/sqlstore v0.0.5 h1:WZvu54baa8+9n1sKQe9GuxBVwSISw+xCkw4VFSwwIs8= -github.com/Teajey/sqlstore v0.0.5/go.mod h1:hjk0S593/2Q4QxkEXCgpThj9w5KWGTQi9JtgfziHXXk= +github.com/Teajey/rsvp v0.11.0 h1:ePx7Oj0nDYRCmFB6fQvSQbYUcWhgqTh9fh8PaHlOnwk= +github.com/Teajey/rsvp v0.11.0/go.mod h1:WCWos0l+K/9heUuvbIUXkKAHAtxoLpkJ43C/fszD4RY= github.com/Teajey/sqlstore v0.0.6 h1:kUEpA+3BKFHZl128MuMeYY6zVcmq1QmOlNyofcFEJOA= github.com/Teajey/sqlstore v0.0.6/go.mod h1:hjk0S593/2Q4QxkEXCgpThj9w5KWGTQi9JtgfziHXXk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= -github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= -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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -modernc.org/libc v1.37.6 h1:orZH3c5wmhIQFTXF+Nt+eeauyd+ZIt2BX6ARe+kD+aw= -modernc.org/libc v1.37.6/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= -modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= -modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= diff --git a/http/response/handler.go b/http/response/handler.go index 43c3fa7..1a8585b 100644 --- a/http/response/handler.go +++ b/http/response/handler.go @@ -1,20 +1,25 @@ package response import ( + "fmt" + "lishwist/http/templates" "log" "net/http" + "github.com/Teajey/rsvp" "github.com/Teajey/sqlstore" ) type ServeMux struct { - inner *http.ServeMux + inner *rsvp.ServeMux store *sqlstore.Store } func NewServeMux(store *sqlstore.Store) *ServeMux { + mux := rsvp.NewServeMux() + mux.Config.HtmlTemplate = templates.Templates["login.gotmpl"] return &ServeMux{ - inner: http.NewServeMux(), + inner: mux, store: store, } } @@ -24,29 +29,27 @@ func (m *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { } type Handler interface { - ServeHTTP(h http.Header, r *Request) Response + ServeHTTP(*Session, http.Header, *http.Request) rsvp.Response } -type HandlerFunc func(h http.Header, r *Request) Response +type HandlerFunc func(*Session, http.Header, *http.Request) rsvp.Response func (m *ServeMux) HandleFunc(pattern string, handler HandlerFunc) { - m.inner.HandleFunc(pattern, func(w http.ResponseWriter, stdReq *http.Request) { - r := wrapStdRequest(m.store, stdReq) + m.inner.Std.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { + session := m.GetSession(r) - response := handler(w.Header(), &r) + response := handler(session, w.Header(), r) - err := response.Write(w, stdReq) - if err != nil { - response.Data = struct{ Message error }{err} - response.HtmlTemplateName = "error_page.gotmpl" - response.Status = http.StatusInternalServerError - } else { - return + if session.written { + err := session.inner.Save(r, w) + if err != nil { + log.Printf("Failed to save session: %s\n", err) + } } - err = response.Write(w, stdReq) + + err := response.Write(w, r, m.inner.Config) if err != nil { - log.Printf("Failed to write response.Response to bytes: %s\n", err) - http.Error(w, "Failed to write response", http.StatusInternalServerError) + panic(fmt.Sprintf("Failed to write rsvp.Response: %s", err)) } }) } diff --git a/http/response/request.go b/http/response/request.go index 9ed5b97..a14e51f 100644 --- a/http/response/request.go +++ b/http/response/request.go @@ -1,42 +1,10 @@ package response import ( - "log" "net/http" - "net/url" - - "github.com/Teajey/sqlstore" ) -type Request struct { - inner *http.Request - store *sqlstore.Store -} - -func wrapStdRequest(store *sqlstore.Store, r *http.Request) Request { - return Request{ - inner: r, - store: store, - } -} - -func (r *Request) GetSession() Session { - session, _ := r.store.Get(r.inner, "lishwist_user") - return Session{session} -} - -func (r *Request) ParseForm() url.Values { - err := r.inner.ParseForm() - if err != nil { - log.Printf("Failed to parse form: %s\n", err) - } - return r.inner.Form -} - -func (r *Request) PathValue(name string) string { - return r.inner.PathValue(name) -} - -func (r *Request) URL() *url.URL { - return r.inner.URL +func (m *ServeMux) GetSession(r *http.Request) *Session { + session, _ := m.store.Get(r, "lishwist_user") + return &Session{inner: session} } diff --git a/http/response/response.go b/http/response/response.go index 94f7ddd..b62703e 100644 --- a/http/response/response.go +++ b/http/response/response.go @@ -1,119 +1,26 @@ package response import ( - "bytes" - "encoding/json" "fmt" - "lishwist/http/templates" - "log" "net/http" - "strings" + + "github.com/Teajey/rsvp" ) -type Response struct { - HtmlTemplateName string - Data any - SeeOther string - Session *Session - Status int - LogMessage string +func NotFound() rsvp.Response { + return Error(http.StatusNotFound, "Page not found") } -func (res *Response) Write(w http.ResponseWriter, r *http.Request) error { - if res.LogMessage != "" { - log.Printf("%s --- %s\n", res.Data, res.LogMessage) - } - - if res.Session != nil { - err := res.Session.inner.Save(r, w) - if err != nil { - return fmt.Errorf("Failed to write session: %w", err) - } - } - - if res.SeeOther != "" { - http.Redirect(w, r, res.SeeOther, http.StatusSeeOther) - if res.Session != nil { - flash := res.Session.FlashPeek() - if flash != nil { - err := json.NewEncoder(w).Encode(flash) - if err != nil { - return err - } - } - } - return nil - } - - bodyBytes := bytes.NewBuffer([]byte{}) - accept := r.Header.Get("Accept") - - if res.Status != 0 { - w.WriteHeader(res.Status) - } - - switch { - case strings.Contains(accept, "text/html"): - if res.HtmlTemplateName == "" { - err := json.NewEncoder(bodyBytes).Encode(res.Data) - if err != nil { - return err - } - } else { - err := templates.Execute(bodyBytes, res.HtmlTemplateName, res.Data) - if err != nil { - return err - } - } - case strings.Contains(accept, "application/json"): - err := json.NewEncoder(bodyBytes).Encode(res.Data) - if err != nil { - return err - } - default: - err := json.NewEncoder(bodyBytes).Encode(res.Data) - if err != nil { - return err - } - } - - _, err := w.Write(bodyBytes.Bytes()) - if err != nil { - log.Printf("Failed to write response.Response to HTTP: %s\n", err) - } - return nil -} - -func Data(htmlTemplateName string, data any) Response { - return Response{ - HtmlTemplateName: htmlTemplateName, - Data: data, +func Error(status int, format string, a ...any) rsvp.Response { + return rsvp.Response{ + Body: fmt.Sprintf(format, a...), + Status: status, } } -func (r Response) Log(format string, a ...any) Response { - r.LogMessage = fmt.Sprintf(format, a...) - return r -} - -func (r Response) LogError(err error) Response { - r.LogMessage = fmt.Sprintf("%s", err) - return r -} - -func (r Response) SaveSession(s Session) Response { - r.Session = &s - return r -} - -func SeeOther(url string) Response { - return Response{SeeOther: url} -} - -func Error(status int, format string, a ...any) Response { - return Response{ - Status: status, - HtmlTemplateName: "error_page.gotmpl", - Data: struct{ Message string }{fmt.Sprintf(format, a...)}, +func Data(templateName string, body any) rsvp.Response { + return rsvp.Response{ + Body: body, + TemplateName: templateName, } } diff --git a/http/response/session.go b/http/response/session.go index 0399994..dd8010c 100644 --- a/http/response/session.go +++ b/http/response/session.go @@ -5,11 +5,13 @@ import ( ) type Session struct { - inner *sessions.Session + inner *sessions.Session + written bool } func (s *Session) FlashGet() any { list := s.inner.Flashes() + s.written = true if len(list) < 1 { return nil } else { @@ -32,14 +34,17 @@ func (s *Session) FlashPeek() any { func (s *Session) FlashSet(value any) { s.inner.AddFlash(value) + s.written = true } func (s *Session) SetID(value string) { s.inner.ID = value + s.written = true } func (s *Session) SetValue(key any, value any) { s.inner.Values[key] = value + s.written = true } func (s *Session) GetValue(key any) any { @@ -48,6 +53,7 @@ func (s *Session) GetValue(key any) any { func (s *Session) ClearValues() { s.inner.Values = nil + s.written = true } func (s *Session) Options() *sessions.Options { diff --git a/http/routing/context.go b/http/routing/context.go index f02f041..646bcd2 100644 --- a/http/routing/context.go +++ b/http/routing/context.go @@ -1,26 +1,30 @@ package routing import ( + "log" "net/http" lishwist "lishwist/core" "lishwist/http/response" + + "github.com/Teajey/rsvp" ) -func ExpectAppSession(next func(*lishwist.Session, http.Header, *response.Request) response.Response) response.HandlerFunc { - return func(w http.Header, r *response.Request) response.Response { - session := r.GetSession() +func ExpectAppSession(next func(*lishwist.Session, http.Header, *http.Request) rsvp.Response) response.HandlerFunc { + return func(session *response.Session, h http.Header, r *http.Request) rsvp.Response { username, ok := session.GetValue("username").(string) if !ok { - return response.Error(http.StatusInternalServerError, "Something went wrong.").Log("Failed to get username from session") + log.Printf("Failed to get username from session\n") + return response.Error(http.StatusInternalServerError, "Something went wrong.") } appSession, err := lishwist.SessionFromUsername(username) if err != nil { - return response.Error(http.StatusInternalServerError, "Something went wrong.").Log("Failed to get session by username %q: %s", username, err) + log.Printf("Failed to get session by username %q: %s\n", username, err) + return response.Error(http.StatusInternalServerError, "Something went wrong.") } - return next(appSession, w, r) + return next(appSession, h, r) } } diff --git a/http/routing/error.go b/http/routing/error.go deleted file mode 100644 index 91fa5d0..0000000 --- a/http/routing/error.go +++ /dev/null @@ -1,17 +0,0 @@ -package routing - -import ( - "fmt" - "log" - "net/http" - "strings" -) - -func writeGeneralErrorJson(w http.ResponseWriter, status int, format string, a ...any) { - msg := fmt.Sprintf(format, a...) - log.Printf("General error: %s\n", msg) - w.Header().Add("Content-Type", "application/json") - w.WriteHeader(status) - escapedMsg := strings.ReplaceAll(msg, `"`, `\"`) - _, _ = w.Write([]byte(fmt.Sprintf(`{"GeneralError":"%s"}`, escapedMsg))) -} diff --git a/http/routing/foreign_wishlist.go b/http/routing/foreign_wishlist.go index 5b1d2d9..3cb76b6 100644 --- a/http/routing/foreign_wishlist.go +++ b/http/routing/foreign_wishlist.go @@ -3,7 +3,10 @@ package routing import ( lishwist "lishwist/core" "lishwist/http/response" + "log" "net/http" + + "github.com/Teajey/rsvp" ) type foreignWishlistProps struct { @@ -13,21 +16,23 @@ type foreignWishlistProps struct { Gifts []lishwist.Wish } -func ForeignWishlist(app *lishwist.Session, h http.Header, r *response.Request) response.Response { +func ForeignWishlist(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response { userReference := r.PathValue("userReference") if app.User.Reference == userReference { - return response.SeeOther("/") + return rsvp.SeeOther("/") } otherUser, err := lishwist.GetUserByReference(userReference) if err != nil { - return response.Error(http.StatusInternalServerError, "An error occurred while fetching this user :(").Log("Couldn't get user by reference %q: %s", userReference, err) + log.Printf("Couldn't get user by reference %q: %s\n", userReference, err) + return response.Error(http.StatusInternalServerError, "An error occurred while fetching this user :(") } if otherUser == nil { return response.Error(http.StatusInternalServerError, "User not found") } wishes, err := app.GetOthersWishes(userReference) if err != nil { - return response.Error(http.StatusInternalServerError, "An error occurred while fetching this user :(").Log("%q couldn't get wishes of other user %q: %s", app.User.Name, otherUser.Name, err) + log.Printf("%q couldn't get wishes of other user %q: %s\n", app.User.Name, otherUser.Name, err) + return response.Error(http.StatusInternalServerError, "An error occurred while fetching this user :(") } p := foreignWishlistProps{CurrentUserId: app.User.Id, CurrentUserName: app.User.Name, Username: otherUser.Name, Gifts: wishes} return response.Data("foreign_wishlist.gotmpl", p) @@ -38,18 +43,20 @@ type publicWishlistProps struct { GiftCount int } -func PublicWishlist(h http.Header, r *response.Request) response.Response { +func PublicWishlist(s *response.Session, h http.Header, r *http.Request) rsvp.Response { userReference := r.PathValue("userReference") otherUser, err := lishwist.GetUserByReference(userReference) if err != nil { - return response.Error(http.StatusInternalServerError, "An error occurred while fetching this user :(").Log("Couldn't get user by reference %q on public wishlist: %s", userReference, err) + log.Printf("Couldn't get user by reference %q on public wishlist: %s\n", userReference, err) + return response.Error(http.StatusInternalServerError, "An error occurred while fetching this user :(") } if otherUser == nil { return response.Error(http.StatusInternalServerError, "User not found") } giftCount, err := otherUser.WishCount() if err != nil { - return response.Error(http.StatusInternalServerError, "An error occurred while fetching this user :(").Log("Couldn't get wishes of user %q on public wishlist: %s", otherUser.Name, err) + log.Printf("Couldn't get wishes of user %q on public wishlist: %s\n", otherUser.Name, err) + return response.Error(http.StatusInternalServerError, "An error occurred while fetching this user :(") } p := publicWishlistProps{Username: otherUser.Name, GiftCount: giftCount} return response.Data("public_foreign_wishlist.gotmpl", p) diff --git a/http/routing/groups.go b/http/routing/groups.go index c2756f1..cdf061e 100644 --- a/http/routing/groups.go +++ b/http/routing/groups.go @@ -1,11 +1,14 @@ package routing import ( + "log" "net/http" "slices" lishwist "lishwist/core" "lishwist/http/response" + + "github.com/Teajey/rsvp" ) type GroupProps struct { @@ -13,7 +16,7 @@ type GroupProps struct { CurrentUsername string } -func AdminGroup(app *lishwist.Session, h http.Header, r *response.Request) response.Response { +func AdminGroup(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response { reference := r.PathValue("groupReference") group, err := app.GetGroupByReference(reference) if err != nil { @@ -33,14 +36,15 @@ func AdminGroup(app *lishwist.Session, h http.Header, r *response.Request) respo return response.Data("group_page.gotmpl", p) } -func Group(app *lishwist.Session, h http.Header, r *response.Request) response.Response { +func Group(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response { if app.User.IsAdmin { return AdminGroup(app, h, r) } groupReference := r.PathValue("groupReference") group, err := app.GetGroupByReference(groupReference) if err != nil { - return response.Error(http.StatusInternalServerError, "An error occurred while fetching this group :(").Log("Couldn't get group: %s", err) + log.Printf("Couldn't get group: %s\n", err) + return response.Error(http.StatusInternalServerError, "An error occurred while fetching this group :(") } if group == nil { return response.Error(http.StatusNotFound, "Group not found. (It might be because you're not a member)") @@ -54,11 +58,12 @@ func Group(app *lishwist.Session, h http.Header, r *response.Request) response.R return response.Data("group_page.gotmpl", p) } -func PublicGroup(h http.Header, r *response.Request) response.Response { +func PublicGroup(s *response.Session, h http.Header, r *http.Request) rsvp.Response { groupReference := r.PathValue("groupReference") group, err := lishwist.GetGroupByReference(groupReference) if err != nil { - return response.Error(http.StatusInternalServerError, "An error occurred while fetching this group :(").Log("Couldn't get group: %s", err) + log.Printf("Couldn't get group: %s\n", err) + return response.Error(http.StatusInternalServerError, "An error occurred while fetching this group :(") } p := GroupProps{ Group: group, @@ -66,19 +71,23 @@ func PublicGroup(h http.Header, r *response.Request) response.Response { return response.Data("public_group_page.gotmpl", p) } -func GroupPost(app *lishwist.Session, h http.Header, r *response.Request) response.Response { +func GroupPost(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response { admin := app.Admin() if admin == nil { - return NotFound(h, r) + return response.NotFound() + } + + err := r.ParseForm() + if err != nil { + return response.Error(http.StatusBadRequest, "Failed to parse form") } - form := r.ParseForm() var group *lishwist.Group reference := r.PathValue("groupReference") - name := form.Get("name") - addUsers := form["addUser"] - removeUsers := form["removeUser"] + name := r.Form.Get("name") + addUsers := r.Form["addUser"] + removeUsers := r.Form["removeUser"] if name != "" { createdGroup, err := admin.CreateGroup(name, reference) @@ -92,7 +101,7 @@ func GroupPost(app *lishwist.Session, h http.Header, r *response.Request) respon return response.Error(http.StatusInternalServerError, "Failed to get group: %s", err) } if existingGroup == nil { - return response.Error(http.StatusNotFound, "Group not found", err) + return response.Error(http.StatusNotFound, "Group not found: %s", err) } group = existingGroup @@ -127,10 +136,10 @@ func GroupPost(app *lishwist.Session, h http.Header, r *response.Request) respon return response.Data("", group) } -func Groups(app *lishwist.Session, h http.Header, r *response.Request) response.Response { +func Groups(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response { admin := app.Admin() if admin == nil { - return NotFound(h, r) + return response.NotFound() } groups, err := admin.ListGroups() diff --git a/http/routing/home.go b/http/routing/home.go index c7f268c..7c68e01 100644 --- a/http/routing/home.go +++ b/http/routing/home.go @@ -1,11 +1,14 @@ package routing import ( + "log" "net/http" lishwist "lishwist/core" "lishwist/http/env" "lishwist/http/response" + + "github.com/Teajey/rsvp" ) type HomeProps struct { @@ -17,26 +20,33 @@ type HomeProps struct { Groups []lishwist.Group } -func Home(app *lishwist.Session, h http.Header, r *response.Request) response.Response { +func Home(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response { gifts, err := app.GetWishes() if err != nil { - return response.Error(http.StatusInternalServerError, "An error occurred while fetching your wishlist :(").Log("Failed to get gifts: %s", err) + log.Printf("Failed to get gifts: %s\n", err) + return response.Error(http.StatusInternalServerError, "An error occurred while fetching your wishlist :(") } todo, err := app.GetTodo() if err != nil { - return response.Error(http.StatusInternalServerError, "An error occurred while fetching your wishlist :(").Log("Failed to get todo: %s", err) + log.Printf("Failed to get todo: %s\n", err) + return response.Error(http.StatusInternalServerError, "An error occurred while fetching your wishlist :(") } groups, err := app.GetGroups() if err != nil { - return response.Error(http.StatusInternalServerError, "An error occurred while fetching your wishlist :(").Log("Failed to get groups: %s", err) + log.Printf("Failed to get groups: %s\n", err) + return response.Error(http.StatusInternalServerError, "An error occurred while fetching your wishlist :(") } p := HomeProps{Username: app.User.Name, Gifts: gifts, Todo: todo, Reference: app.User.Reference, HostUrl: env.HostUrl.String(), Groups: groups} return response.Data("home.gotmpl", p) } -func HomePost(app *lishwist.Session, h http.Header, r *response.Request) response.Response { - form := r.ParseForm() - switch form.Get("intent") { +func HomePost(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response { + err := r.ParseForm() + if err != nil { + return response.Error(http.StatusBadRequest, "Failed to parse form") + } + + switch r.Form.Get("intent") { case "add_idea": return WishlistAdd(app, h, r) case "delete_idea": diff --git a/http/routing/login.go b/http/routing/login.go index c2434c1..5a34fd4 100644 --- a/http/routing/login.go +++ b/http/routing/login.go @@ -1,19 +1,20 @@ package routing import ( + "log" "net/http" lishwist "lishwist/core" "lishwist/http/api" "lishwist/http/response" + + "github.com/Teajey/rsvp" ) -func Login(h http.Header, r *response.Request) response.Response { - session := r.GetSession() - +func Login(s *response.Session, h http.Header, r *http.Request) rsvp.Response { props := api.NewLoginProps("", "") - flash := session.FlashGet() + flash := s.FlashGet() flashProps, ok := flash.(*api.LoginProps) if ok { props.Username.Value = flashProps.Username.Value @@ -23,21 +24,23 @@ func Login(h http.Header, r *response.Request) response.Response { props.Password.Error = flashProps.Password.Error } - flash = session.FlashGet() + flash = s.FlashGet() successfulReg, _ := flash.(bool) if successfulReg { props.SuccessfulRegistration = true } - return response.Data("login.gotmpl", props).SaveSession(session) + return rsvp.Response{TemplateName: "login.gotmpl", Body: props} } -func LoginPost(h http.Header, r *response.Request) response.Response { - form := r.ParseForm() - session := r.GetSession() +func LoginPost(session *response.Session, h http.Header, r *http.Request) rsvp.Response { + err := r.ParseForm() + if err != nil { + return response.Error(http.StatusBadRequest, "Failed to parse form") + } - username := form.Get("username") - password := form.Get("password") + username := r.Form.Get("username") + password := r.Form.Get("password") props := api.NewLoginProps(username, password) @@ -45,7 +48,8 @@ func LoginPost(h http.Header, r *response.Request) response.Response { props.Password.Value = "" if !valid { session.FlashSet(&props) - return response.SeeOther("/").SaveSession(session).Log("Invalid props: %#v\n", props) + log.Printf("Invalid props: %#v\n", props) + return rsvp.SeeOther("/") } app, err := lishwist.Login(username, password) @@ -54,11 +58,13 @@ func LoginPost(h http.Header, r *response.Request) response.Response { case lishwist.ErrorInvalidCredentials: props.GeneralError = "Username or password invalid" session.FlashSet(&props) - return response.SeeOther("/").SaveSession(session).Log("Invalid credentials: %s: %#v\n", err, props) + log.Printf("Invalid credentials: %s: %#v\n", err, props) + return rsvp.SeeOther("/") default: props.GeneralError = "Something went wrong." session.FlashSet(&props) - return response.SeeOther("/").SaveSession(session).Log("Login error: %s\n", err) + log.Printf("Login error: %s\n", err) + return rsvp.SeeOther("/") } } @@ -66,5 +72,5 @@ func LoginPost(h http.Header, r *response.Request) response.Response { session.SetValue("authorized", true) session.SetValue("username", app.User.Name) - return response.SeeOther(r.URL().Path).SaveSession(session) + return rsvp.SeeOther(r.URL.Path) } diff --git a/http/routing/logout.go b/http/routing/logout.go index d25638b..0500047 100644 --- a/http/routing/logout.go +++ b/http/routing/logout.go @@ -3,13 +3,13 @@ package routing import ( "lishwist/http/response" "net/http" + + "github.com/Teajey/rsvp" ) -func LogoutPost(h http.Header, r *response.Request) response.Response { - session := r.GetSession() - +func LogoutPost(session *response.Session, h http.Header, r *http.Request) rsvp.Response { session.Options().MaxAge = 0 session.ClearValues() - return response.SeeOther("/").SaveSession(session) + return rsvp.SeeOther("/") } diff --git a/http/routing/not_found.go b/http/routing/not_found.go index bc68387..5ebc6c4 100644 --- a/http/routing/not_found.go +++ b/http/routing/not_found.go @@ -4,8 +4,10 @@ import ( "net/http" "lishwist/http/response" + + "github.com/Teajey/rsvp" ) -func NotFound(h http.Header, r *response.Request) response.Response { +func NotFound(s *response.Session, h http.Header, r *http.Request) rsvp.Response { return response.Error(http.StatusNotFound, "Page not found") } diff --git a/http/routing/register.go b/http/routing/register.go index d9e15ec..add5b73 100644 --- a/http/routing/register.go +++ b/http/routing/register.go @@ -5,13 +5,15 @@ import ( lishwist "lishwist/core" "lishwist/http/api" "lishwist/http/response" + "log" "net/http" + + "github.com/Teajey/rsvp" ) -func Register(h http.Header, r *response.Request) response.Response { +func Register(session *response.Session, h http.Header, r *http.Request) rsvp.Response { props := api.NewRegisterProps("", "", "") - session := r.GetSession() flash := session.FlashGet() flashProps, _ := flash.(*api.RegisterProps) @@ -23,16 +25,18 @@ func Register(h http.Header, r *response.Request) response.Response { props.ConfirmPassword.Error = flashProps.ConfirmPassword.Error } - return response.Data("register.gotmpl", props).SaveSession(session) + return response.Data("register.gotmpl", props) } -func RegisterPost(h http.Header, r *response.Request) response.Response { - form := r.ParseForm() - s := r.GetSession() +func RegisterPost(s *response.Session, h http.Header, r *http.Request) rsvp.Response { + err := r.ParseForm() + if err != nil { + return response.Error(http.StatusBadRequest, "Failed to parse form") + } - username := form.Get("username") - newPassword := form.Get("newPassword") - confirmPassword := form.Get("confirmPassword") + username := r.Form.Get("username") + newPassword := r.Form.Get("newPassword") + confirmPassword := r.Form.Get("confirmPassword") props := api.NewRegisterProps(username, newPassword, confirmPassword) @@ -41,10 +45,11 @@ func RegisterPost(h http.Header, r *response.Request) response.Response { props.ConfirmPassword.Value = "" if !valid { s.FlashSet(&props) - return response.SeeOther("/").SaveSession(s).Log("Invalid props: %#v\n", props) + log.Printf("Invalid props: %#v\n", props) + return rsvp.SeeOther("/") } - _, err := lishwist.Register(username, newPassword) + _, err = lishwist.Register(username, newPassword) if err != nil { if errors.Is(err, lishwist.ErrorUsernameTaken) { props.Username.Error = "Username is taken" @@ -52,9 +57,10 @@ func RegisterPost(h http.Header, r *response.Request) response.Response { props.GeneralError = "Something went wrong." } s.FlashSet(&props) - return response.SeeOther("/register").SaveSession(s).Log("Registration failed: %s\n", err) + log.Printf("Registration failed: %s\n", err) + return rsvp.SeeOther("/register") } s.FlashSet(true) - return response.SeeOther("/").SaveSession(s) + return rsvp.SeeOther("/") } diff --git a/http/routing/todo.go b/http/routing/todo.go index a56e500..5daab4f 100644 --- a/http/routing/todo.go +++ b/http/routing/todo.go @@ -1,29 +1,38 @@ package routing import ( + "log" + "net/http" + + "github.com/Teajey/rsvp" + lishwist "lishwist/core" "lishwist/http/response" - "net/http" ) -func TodoUpdate(app *lishwist.Session, h http.Header, r *response.Request) response.Response { - form := r.ParseForm() +func TodoUpdate(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response { + err := r.ParseForm() + if err != nil { + return response.Error(http.StatusBadRequest, "Failed to parse form") + } - switch form.Get("intent") { + switch r.Form.Get("intent") { case "unclaim_todo": - unclaims := form["gift"] + unclaims := r.Form["gift"] err := app.ClaimWishes([]string{}, unclaims) if err != nil { - return response.Error(http.StatusInternalServerError, "Failed to update claim...").LogError(err) + log.Printf("%s\n", err) + return response.Error(http.StatusInternalServerError, "Failed to update claim...") } case "complete_todo": - claims := form["gift"] + claims := r.Form["gift"] err := app.CompleteWishes(claims) if err != nil { - return response.Error(http.StatusInternalServerError, "Failed to complete gifts...").LogError(err) + log.Printf("%s\n", err) + return response.Error(http.StatusInternalServerError, "Failed to complete gifts...") } default: return response.Error(http.StatusBadRequest, "Invalid intent") } - return response.SeeOther("/") + return rsvp.SeeOther("/") } diff --git a/http/routing/users.go b/http/routing/users.go index bdada5b..f1d509b 100644 --- a/http/routing/users.go +++ b/http/routing/users.go @@ -4,12 +4,14 @@ import ( lishwist "lishwist/core" "lishwist/http/response" "net/http" + + "github.com/Teajey/rsvp" ) -func Users(app *lishwist.Session, h http.Header, r *response.Request) response.Response { +func Users(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response { admin := app.Admin() if admin == nil { - return NotFound(h, r) + return response.NotFound() } users, err := admin.ListUsers() @@ -20,10 +22,10 @@ func Users(app *lishwist.Session, h http.Header, r *response.Request) response.R return response.Data("", users) } -func User(app *lishwist.Session, h http.Header, r *response.Request) response.Response { +func User(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response { admin := app.Admin() if admin == nil { - return NotFound(h, r) + return response.NotFound() } reference := r.PathValue("userReference") @@ -39,13 +41,16 @@ func User(app *lishwist.Session, h http.Header, r *response.Request) response.Re return response.Data("", user) } -func UserPost(app *lishwist.Session, h http.Header, r *response.Request) response.Response { +func UserPost(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response { admin := app.Admin() if admin == nil { - return NotFound(h, r) + return response.NotFound() } - form := r.ParseForm() + err := r.ParseForm() + if err != nil { + return response.Error(http.StatusBadRequest, "Failed to parse form") + } reference := r.PathValue("userReference") if reference == app.User.Reference { @@ -60,7 +65,7 @@ func UserPost(app *lishwist.Session, h http.Header, r *response.Request) respons return response.Error(http.StatusNotFound, "User not found") } - intent := form.Get("intent") + intent := r.Form.Get("intent") if intent != "" { err = admin.UserSetLive(reference, intent != "delete") diff --git a/http/routing/wishlist.go b/http/routing/wishlist.go index d0363cf..a1015c9 100644 --- a/http/routing/wishlist.go +++ b/http/routing/wishlist.go @@ -1,68 +1,90 @@ package routing import ( + "log" + "net/http" + + "github.com/Teajey/rsvp" + lishwist "lishwist/core" "lishwist/http/response" - "net/http" ) -func WishlistAdd(app *lishwist.Session, h http.Header, r *response.Request) response.Response { - form := r.ParseForm() - newGiftName := form.Get("gift_name") - err := app.MakeWish(newGiftName) +func WishlistAdd(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response { + err := r.ParseForm() if err != nil { - return response.Error(http.StatusInternalServerError, "Failed to add gift.").LogError(err) + return response.Error(http.StatusBadRequest, "Failed to parse form") } - return response.SeeOther("/") + + newGiftName := r.Form.Get("gift_name") + err = app.MakeWish(newGiftName) + if err != nil { + log.Printf("%s\n", err) + return response.Error(http.StatusInternalServerError, "Failed to add gift.") + } + return rsvp.SeeOther("/") } -func WishlistDelete(app *lishwist.Session, h http.Header, r *response.Request) response.Response { - form := r.ParseForm() - targets := form["gift"] - err := app.RevokeWishes(targets...) +func WishlistDelete(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response { + err := r.ParseForm() if err != nil { - return response.Error(http.StatusInternalServerError, "Failed to remove gifts.").LogError(err) + return response.Error(http.StatusBadRequest, "Failed to parse form") } - return response.SeeOther("/") + + targets := r.Form["gift"] + err = app.RevokeWishes(targets...) + if err != nil { + log.Printf("%s\n", err) + return response.Error(http.StatusInternalServerError, "Failed to remove gifts.") + } + return rsvp.SeeOther("/") } -func ForeignWishlistPost(app *lishwist.Session, h http.Header, r *response.Request) response.Response { - form := r.ParseForm() +func ForeignWishlistPost(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response { + err := r.ParseForm() + if err != nil { + return response.Error(http.StatusBadRequest, "Failed to parse form") + } + userReference := r.PathValue("userReference") - intent := form.Get("intent") + intent := r.Form.Get("intent") switch intent { case "claim": - claims := form["unclaimed"] - unclaims := form["claimed"] + claims := r.Form["unclaimed"] + unclaims := r.Form["claimed"] err := app.ClaimWishes(claims, unclaims) if err != nil { - return response.Error(http.StatusInternalServerError, "Failed to update claim...").LogError(err) + log.Printf("%s\n", err) + return response.Error(http.StatusInternalServerError, "Failed to update claim...") } case "complete": - claims := form["claimed"] + claims := r.Form["claimed"] err := app.CompleteWishes(claims) if err != nil { - return response.Error(http.StatusInternalServerError, "Failed to complete gifts...").LogError(err) + log.Printf("%s\n", err) + return response.Error(http.StatusInternalServerError, "Failed to complete gifts...") } case "add": - wishName := form.Get("gift_name") + wishName := r.Form.Get("gift_name") if wishName == "" { return response.Error(http.StatusBadRequest, "Gift name not provided") } err := app.SuggestWishForUser(userReference, wishName) if err != nil { - return response.Error(http.StatusInternalServerError, "Failed to add gift idea to other user...").LogError(err) + log.Printf("%s\n", err) + return response.Error(http.StatusInternalServerError, "Failed to add gift idea to other user...") } case "delete": - claims := form["unclaimed"] - unclaims := form["claimed"] + claims := r.Form["unclaimed"] + unclaims := r.Form["claimed"] gifts := append(claims, unclaims...) err := app.RecindWishesForUser(gifts...) if err != nil { - return response.Error(http.StatusInternalServerError, "Failed to remove gift idea for other user...").LogError(err) + log.Printf("%s\n", err) + return response.Error(http.StatusInternalServerError, "Failed to remove gift idea for other user...") } default: return response.Error(http.StatusBadRequest, "Invalid intent %q", intent) } - return response.SeeOther("/list/" + userReference) + return rsvp.SeeOther("/list/" + userReference) } diff --git a/http/templates/templates.go b/http/templates/templates.go index 4a77340..fe6bd5b 100644 --- a/http/templates/templates.go +++ b/http/templates/templates.go @@ -2,8 +2,7 @@ package templates import ( "fmt" - "io" - "text/template" + "html/template" ) type InputProps struct { @@ -30,17 +29,13 @@ func (p *InputProps) Validate() bool { return true } -var tmpls map[string]*template.Template = loadTemplates() +var Templates map[string]*template.Template -func Execute(w io.Writer, name string, data any) error { - err := tmpls[name].Execute(w, data) - if err != nil { - return fmt.Errorf("Failed to execute '%s' template: %w\n", name, err) - } - return nil +func init() { + Templates = load() } -func loadTemplates() map[string]*template.Template { +func load() map[string]*template.Template { homeTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/home.gotmpl")) loginTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/login.gotmpl")) registerTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/register.gotmpl"))