Use RSVP 0.11.0

This commit is contained in:
Teajey 2025-08-20 14:04:33 +09:00
parent d33c02a5ac
commit dfa2525714
Signed by: Teajey
GPG Key ID: 970E790FE834A713
19 changed files with 244 additions and 340 deletions

View File

@ -1,25 +1,14 @@
module lishwist/http module lishwist/http
go 1.23 go 1.23.3
toolchain go1.23.3 toolchain go1.24.5
require ( require (
github.com/Teajey/rsvp v0.11.0
github.com/Teajey/sqlstore v0.0.6 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 github.com/gorilla/sessions v1.4.0
golang.org/x/crypto v0.22.0 golang.org/x/crypto v0.22.0
) )
require ( require github.com/gorilla/securecookie v1.1.2 // indirect
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
)

View File

@ -1,39 +1,12 @@
github.com/Teajey/sqlstore v0.0.3 h1:6Y1jz9/yw1cj/Z/jrii0s87RAomKWr/07B1auDgw8pg= github.com/Teajey/rsvp v0.11.0 h1:ePx7Oj0nDYRCmFB6fQvSQbYUcWhgqTh9fh8PaHlOnwk=
github.com/Teajey/sqlstore v0.0.3/go.mod h1:hjk0S593/2Q4QxkEXCgpThj9w5KWGTQi9JtgfziHXXk= github.com/Teajey/rsvp v0.11.0/go.mod h1:WCWos0l+K/9heUuvbIUXkKAHAtxoLpkJ43C/fszD4RY=
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/sqlstore v0.0.6 h1:kUEpA+3BKFHZl128MuMeYY6zVcmq1QmOlNyofcFEJOA= github.com/Teajey/sqlstore v0.0.6 h1:kUEpA+3BKFHZl128MuMeYY6zVcmq1QmOlNyofcFEJOA=
github.com/Teajey/sqlstore v0.0.6/go.mod h1:hjk0S593/2Q4QxkEXCgpThj9w5KWGTQi9JtgfziHXXk= 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 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 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 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= 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 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 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=

View File

@ -1,20 +1,25 @@
package response package response
import ( import (
"fmt"
"lishwist/http/templates"
"log" "log"
"net/http" "net/http"
"github.com/Teajey/rsvp"
"github.com/Teajey/sqlstore" "github.com/Teajey/sqlstore"
) )
type ServeMux struct { type ServeMux struct {
inner *http.ServeMux inner *rsvp.ServeMux
store *sqlstore.Store store *sqlstore.Store
} }
func NewServeMux(store *sqlstore.Store) *ServeMux { func NewServeMux(store *sqlstore.Store) *ServeMux {
mux := rsvp.NewServeMux()
mux.Config.HtmlTemplate = templates.Templates["login.gotmpl"]
return &ServeMux{ return &ServeMux{
inner: http.NewServeMux(), inner: mux,
store: store, store: store,
} }
} }
@ -24,29 +29,27 @@ func (m *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
type Handler interface { 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) { func (m *ServeMux) HandleFunc(pattern string, handler HandlerFunc) {
m.inner.HandleFunc(pattern, func(w http.ResponseWriter, stdReq *http.Request) { m.inner.Std.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
r := wrapStdRequest(m.store, stdReq) session := m.GetSession(r)
response := handler(w.Header(), &r) response := handler(session, w.Header(), r)
err := response.Write(w, stdReq) if session.written {
err := session.inner.Save(r, w)
if err != nil { if err != nil {
response.Data = struct{ Message error }{err} log.Printf("Failed to save session: %s\n", err)
response.HtmlTemplateName = "error_page.gotmpl"
response.Status = http.StatusInternalServerError
} else {
return
} }
err = response.Write(w, stdReq) }
err := response.Write(w, r, m.inner.Config)
if err != nil { if err != nil {
log.Printf("Failed to write response.Response to bytes: %s\n", err) panic(fmt.Sprintf("Failed to write rsvp.Response: %s", err))
http.Error(w, "Failed to write response", http.StatusInternalServerError)
} }
}) })
} }

View File

@ -1,42 +1,10 @@
package response package response
import ( import (
"log"
"net/http" "net/http"
"net/url"
"github.com/Teajey/sqlstore"
) )
type Request struct { func (m *ServeMux) GetSession(r *http.Request) *Session {
inner *http.Request session, _ := m.store.Get(r, "lishwist_user")
store *sqlstore.Store return &Session{inner: session}
}
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
} }

View File

@ -1,119 +1,26 @@
package response package response
import ( import (
"bytes"
"encoding/json"
"fmt" "fmt"
"lishwist/http/templates"
"log"
"net/http" "net/http"
"strings"
"github.com/Teajey/rsvp"
) )
type Response struct { func NotFound() rsvp.Response {
HtmlTemplateName string return Error(http.StatusNotFound, "Page not found")
Data any
SeeOther string
Session *Session
Status int
LogMessage string
} }
func (res *Response) Write(w http.ResponseWriter, r *http.Request) error { func Error(status int, format string, a ...any) rsvp.Response {
if res.LogMessage != "" { return rsvp.Response{
log.Printf("%s --- %s\n", res.Data, res.LogMessage) Body: fmt.Sprintf(format, a...),
}
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 (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, 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,
} }
} }

View File

@ -6,10 +6,12 @@ import (
type Session struct { type Session struct {
inner *sessions.Session inner *sessions.Session
written bool
} }
func (s *Session) FlashGet() any { func (s *Session) FlashGet() any {
list := s.inner.Flashes() list := s.inner.Flashes()
s.written = true
if len(list) < 1 { if len(list) < 1 {
return nil return nil
} else { } else {
@ -32,14 +34,17 @@ func (s *Session) FlashPeek() any {
func (s *Session) FlashSet(value any) { func (s *Session) FlashSet(value any) {
s.inner.AddFlash(value) s.inner.AddFlash(value)
s.written = true
} }
func (s *Session) SetID(value string) { func (s *Session) SetID(value string) {
s.inner.ID = value s.inner.ID = value
s.written = true
} }
func (s *Session) SetValue(key any, value any) { func (s *Session) SetValue(key any, value any) {
s.inner.Values[key] = value s.inner.Values[key] = value
s.written = true
} }
func (s *Session) GetValue(key any) any { func (s *Session) GetValue(key any) any {
@ -48,6 +53,7 @@ func (s *Session) GetValue(key any) any {
func (s *Session) ClearValues() { func (s *Session) ClearValues() {
s.inner.Values = nil s.inner.Values = nil
s.written = true
} }
func (s *Session) Options() *sessions.Options { func (s *Session) Options() *sessions.Options {

View File

@ -1,26 +1,30 @@
package routing package routing
import ( import (
"log"
"net/http" "net/http"
lishwist "lishwist/core" lishwist "lishwist/core"
"lishwist/http/response" "lishwist/http/response"
"github.com/Teajey/rsvp"
) )
func ExpectAppSession(next func(*lishwist.Session, http.Header, *response.Request) response.Response) response.HandlerFunc { func ExpectAppSession(next func(*lishwist.Session, http.Header, *http.Request) rsvp.Response) response.HandlerFunc {
return func(w http.Header, r *response.Request) response.Response { return func(session *response.Session, h http.Header, r *http.Request) rsvp.Response {
session := r.GetSession()
username, ok := session.GetValue("username").(string) username, ok := session.GetValue("username").(string)
if !ok { 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) appSession, err := lishwist.SessionFromUsername(username)
if err != nil { 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)
} }
} }

View File

@ -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)))
}

View File

@ -3,7 +3,10 @@ package routing
import ( import (
lishwist "lishwist/core" lishwist "lishwist/core"
"lishwist/http/response" "lishwist/http/response"
"log"
"net/http" "net/http"
"github.com/Teajey/rsvp"
) )
type foreignWishlistProps struct { type foreignWishlistProps struct {
@ -13,21 +16,23 @@ type foreignWishlistProps struct {
Gifts []lishwist.Wish 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") userReference := r.PathValue("userReference")
if app.User.Reference == userReference { if app.User.Reference == userReference {
return response.SeeOther("/") return rsvp.SeeOther("/")
} }
otherUser, err := lishwist.GetUserByReference(userReference) otherUser, err := lishwist.GetUserByReference(userReference)
if err != nil { 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 { if otherUser == nil {
return response.Error(http.StatusInternalServerError, "User not found") return response.Error(http.StatusInternalServerError, "User not found")
} }
wishes, err := app.GetOthersWishes(userReference) wishes, err := app.GetOthersWishes(userReference)
if err != nil { 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} p := foreignWishlistProps{CurrentUserId: app.User.Id, CurrentUserName: app.User.Name, Username: otherUser.Name, Gifts: wishes}
return response.Data("foreign_wishlist.gotmpl", p) return response.Data("foreign_wishlist.gotmpl", p)
@ -38,18 +43,20 @@ type publicWishlistProps struct {
GiftCount int 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") userReference := r.PathValue("userReference")
otherUser, err := lishwist.GetUserByReference(userReference) otherUser, err := lishwist.GetUserByReference(userReference)
if err != nil { 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 { if otherUser == nil {
return response.Error(http.StatusInternalServerError, "User not found") return response.Error(http.StatusInternalServerError, "User not found")
} }
giftCount, err := otherUser.WishCount() giftCount, err := otherUser.WishCount()
if err != nil { 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} p := publicWishlistProps{Username: otherUser.Name, GiftCount: giftCount}
return response.Data("public_foreign_wishlist.gotmpl", p) return response.Data("public_foreign_wishlist.gotmpl", p)

View File

@ -1,11 +1,14 @@
package routing package routing
import ( import (
"log"
"net/http" "net/http"
"slices" "slices"
lishwist "lishwist/core" lishwist "lishwist/core"
"lishwist/http/response" "lishwist/http/response"
"github.com/Teajey/rsvp"
) )
type GroupProps struct { type GroupProps struct {
@ -13,7 +16,7 @@ type GroupProps struct {
CurrentUsername string 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") reference := r.PathValue("groupReference")
group, err := app.GetGroupByReference(reference) group, err := app.GetGroupByReference(reference)
if err != nil { 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) 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 { if app.User.IsAdmin {
return AdminGroup(app, h, r) return AdminGroup(app, h, r)
} }
groupReference := r.PathValue("groupReference") groupReference := r.PathValue("groupReference")
group, err := app.GetGroupByReference(groupReference) group, err := app.GetGroupByReference(groupReference)
if err != nil { 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 { if group == nil {
return response.Error(http.StatusNotFound, "Group not found. (It might be because you're not a member)") 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) 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") groupReference := r.PathValue("groupReference")
group, err := lishwist.GetGroupByReference(groupReference) group, err := lishwist.GetGroupByReference(groupReference)
if err != nil { 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{ p := GroupProps{
Group: group, Group: group,
@ -66,19 +71,23 @@ func PublicGroup(h http.Header, r *response.Request) response.Response {
return response.Data("public_group_page.gotmpl", p) 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() admin := app.Admin()
if admin == nil { 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 var group *lishwist.Group
reference := r.PathValue("groupReference") reference := r.PathValue("groupReference")
name := form.Get("name") name := r.Form.Get("name")
addUsers := form["addUser"] addUsers := r.Form["addUser"]
removeUsers := form["removeUser"] removeUsers := r.Form["removeUser"]
if name != "" { if name != "" {
createdGroup, err := admin.CreateGroup(name, reference) 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) return response.Error(http.StatusInternalServerError, "Failed to get group: %s", err)
} }
if existingGroup == nil { if existingGroup == nil {
return response.Error(http.StatusNotFound, "Group not found", err) return response.Error(http.StatusNotFound, "Group not found: %s", err)
} }
group = existingGroup group = existingGroup
@ -127,10 +136,10 @@ func GroupPost(app *lishwist.Session, h http.Header, r *response.Request) respon
return response.Data("", group) 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() admin := app.Admin()
if admin == nil { if admin == nil {
return NotFound(h, r) return response.NotFound()
} }
groups, err := admin.ListGroups() groups, err := admin.ListGroups()

View File

@ -1,11 +1,14 @@
package routing package routing
import ( import (
"log"
"net/http" "net/http"
lishwist "lishwist/core" lishwist "lishwist/core"
"lishwist/http/env" "lishwist/http/env"
"lishwist/http/response" "lishwist/http/response"
"github.com/Teajey/rsvp"
) )
type HomeProps struct { type HomeProps struct {
@ -17,26 +20,33 @@ type HomeProps struct {
Groups []lishwist.Group 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() gifts, err := app.GetWishes()
if err != nil { 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() todo, err := app.GetTodo()
if err != nil { 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() groups, err := app.GetGroups()
if err != nil { 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} 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) return response.Data("home.gotmpl", p)
} }
func HomePost(app *lishwist.Session, h http.Header, r *response.Request) response.Response { func HomePost(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response {
form := r.ParseForm() err := r.ParseForm()
switch form.Get("intent") { if err != nil {
return response.Error(http.StatusBadRequest, "Failed to parse form")
}
switch r.Form.Get("intent") {
case "add_idea": case "add_idea":
return WishlistAdd(app, h, r) return WishlistAdd(app, h, r)
case "delete_idea": case "delete_idea":

View File

@ -1,19 +1,20 @@
package routing package routing
import ( import (
"log"
"net/http" "net/http"
lishwist "lishwist/core" lishwist "lishwist/core"
"lishwist/http/api" "lishwist/http/api"
"lishwist/http/response" "lishwist/http/response"
"github.com/Teajey/rsvp"
) )
func Login(h http.Header, r *response.Request) response.Response { func Login(s *response.Session, h http.Header, r *http.Request) rsvp.Response {
session := r.GetSession()
props := api.NewLoginProps("", "") props := api.NewLoginProps("", "")
flash := session.FlashGet() flash := s.FlashGet()
flashProps, ok := flash.(*api.LoginProps) flashProps, ok := flash.(*api.LoginProps)
if ok { if ok {
props.Username.Value = flashProps.Username.Value 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 props.Password.Error = flashProps.Password.Error
} }
flash = session.FlashGet() flash = s.FlashGet()
successfulReg, _ := flash.(bool) successfulReg, _ := flash.(bool)
if successfulReg { if successfulReg {
props.SuccessfulRegistration = true 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 { func LoginPost(session *response.Session, h http.Header, r *http.Request) rsvp.Response {
form := r.ParseForm() err := r.ParseForm()
session := r.GetSession() if err != nil {
return response.Error(http.StatusBadRequest, "Failed to parse form")
}
username := form.Get("username") username := r.Form.Get("username")
password := form.Get("password") password := r.Form.Get("password")
props := api.NewLoginProps(username, password) props := api.NewLoginProps(username, password)
@ -45,7 +48,8 @@ func LoginPost(h http.Header, r *response.Request) response.Response {
props.Password.Value = "" props.Password.Value = ""
if !valid { if !valid {
session.FlashSet(&props) 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) app, err := lishwist.Login(username, password)
@ -54,11 +58,13 @@ func LoginPost(h http.Header, r *response.Request) response.Response {
case lishwist.ErrorInvalidCredentials: case lishwist.ErrorInvalidCredentials:
props.GeneralError = "Username or password invalid" props.GeneralError = "Username or password invalid"
session.FlashSet(&props) 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: default:
props.GeneralError = "Something went wrong." props.GeneralError = "Something went wrong."
session.FlashSet(&props) 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("authorized", true)
session.SetValue("username", app.User.Name) session.SetValue("username", app.User.Name)
return response.SeeOther(r.URL().Path).SaveSession(session) return rsvp.SeeOther(r.URL.Path)
} }

View File

@ -3,13 +3,13 @@ package routing
import ( import (
"lishwist/http/response" "lishwist/http/response"
"net/http" "net/http"
"github.com/Teajey/rsvp"
) )
func LogoutPost(h http.Header, r *response.Request) response.Response { func LogoutPost(session *response.Session, h http.Header, r *http.Request) rsvp.Response {
session := r.GetSession()
session.Options().MaxAge = 0 session.Options().MaxAge = 0
session.ClearValues() session.ClearValues()
return response.SeeOther("/").SaveSession(session) return rsvp.SeeOther("/")
} }

View File

@ -4,8 +4,10 @@ import (
"net/http" "net/http"
"lishwist/http/response" "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") return response.Error(http.StatusNotFound, "Page not found")
} }

View File

@ -5,13 +5,15 @@ import (
lishwist "lishwist/core" lishwist "lishwist/core"
"lishwist/http/api" "lishwist/http/api"
"lishwist/http/response" "lishwist/http/response"
"log"
"net/http" "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("", "", "") props := api.NewRegisterProps("", "", "")
session := r.GetSession()
flash := session.FlashGet() flash := session.FlashGet()
flashProps, _ := flash.(*api.RegisterProps) flashProps, _ := flash.(*api.RegisterProps)
@ -23,16 +25,18 @@ func Register(h http.Header, r *response.Request) response.Response {
props.ConfirmPassword.Error = flashProps.ConfirmPassword.Error 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 { func RegisterPost(s *response.Session, h http.Header, r *http.Request) rsvp.Response {
form := r.ParseForm() err := r.ParseForm()
s := r.GetSession() if err != nil {
return response.Error(http.StatusBadRequest, "Failed to parse form")
}
username := form.Get("username") username := r.Form.Get("username")
newPassword := form.Get("newPassword") newPassword := r.Form.Get("newPassword")
confirmPassword := form.Get("confirmPassword") confirmPassword := r.Form.Get("confirmPassword")
props := api.NewRegisterProps(username, newPassword, confirmPassword) props := api.NewRegisterProps(username, newPassword, confirmPassword)
@ -41,10 +45,11 @@ func RegisterPost(h http.Header, r *response.Request) response.Response {
props.ConfirmPassword.Value = "" props.ConfirmPassword.Value = ""
if !valid { if !valid {
s.FlashSet(&props) 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 err != nil {
if errors.Is(err, lishwist.ErrorUsernameTaken) { if errors.Is(err, lishwist.ErrorUsernameTaken) {
props.Username.Error = "Username is taken" 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." props.GeneralError = "Something went wrong."
} }
s.FlashSet(&props) 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) s.FlashSet(true)
return response.SeeOther("/").SaveSession(s) return rsvp.SeeOther("/")
} }

View File

@ -1,29 +1,38 @@
package routing package routing
import ( import (
"log"
"net/http"
"github.com/Teajey/rsvp"
lishwist "lishwist/core" lishwist "lishwist/core"
"lishwist/http/response" "lishwist/http/response"
"net/http"
) )
func TodoUpdate(app *lishwist.Session, h http.Header, r *response.Request) response.Response { func TodoUpdate(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response {
form := r.ParseForm() 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": case "unclaim_todo":
unclaims := form["gift"] unclaims := r.Form["gift"]
err := app.ClaimWishes([]string{}, unclaims) err := app.ClaimWishes([]string{}, unclaims)
if err != nil { 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": case "complete_todo":
claims := form["gift"] claims := r.Form["gift"]
err := app.CompleteWishes(claims) err := app.CompleteWishes(claims)
if err != nil { 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: default:
return response.Error(http.StatusBadRequest, "Invalid intent") return response.Error(http.StatusBadRequest, "Invalid intent")
} }
return response.SeeOther("/") return rsvp.SeeOther("/")
} }

View File

@ -4,12 +4,14 @@ import (
lishwist "lishwist/core" lishwist "lishwist/core"
"lishwist/http/response" "lishwist/http/response"
"net/http" "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() admin := app.Admin()
if admin == nil { if admin == nil {
return NotFound(h, r) return response.NotFound()
} }
users, err := admin.ListUsers() 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) 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() admin := app.Admin()
if admin == nil { if admin == nil {
return NotFound(h, r) return response.NotFound()
} }
reference := r.PathValue("userReference") 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) 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() admin := app.Admin()
if admin == nil { 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") reference := r.PathValue("userReference")
if reference == app.User.Reference { 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") return response.Error(http.StatusNotFound, "User not found")
} }
intent := form.Get("intent") intent := r.Form.Get("intent")
if intent != "" { if intent != "" {
err = admin.UserSetLive(reference, intent != "delete") err = admin.UserSetLive(reference, intent != "delete")

View File

@ -1,68 +1,90 @@
package routing package routing
import ( import (
"log"
"net/http"
"github.com/Teajey/rsvp"
lishwist "lishwist/core" lishwist "lishwist/core"
"lishwist/http/response" "lishwist/http/response"
"net/http"
) )
func WishlistAdd(app *lishwist.Session, h http.Header, r *response.Request) response.Response { func WishlistAdd(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response {
form := r.ParseForm() err := r.ParseForm()
newGiftName := form.Get("gift_name")
err := app.MakeWish(newGiftName)
if err != nil { 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 { func WishlistDelete(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response {
form := r.ParseForm() err := r.ParseForm()
targets := form["gift"]
err := app.RevokeWishes(targets...)
if err != nil { 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 { func ForeignWishlistPost(app *lishwist.Session, h http.Header, r *http.Request) rsvp.Response {
form := r.ParseForm() err := r.ParseForm()
if err != nil {
return response.Error(http.StatusBadRequest, "Failed to parse form")
}
userReference := r.PathValue("userReference") userReference := r.PathValue("userReference")
intent := form.Get("intent") intent := r.Form.Get("intent")
switch intent { switch intent {
case "claim": case "claim":
claims := form["unclaimed"] claims := r.Form["unclaimed"]
unclaims := form["claimed"] unclaims := r.Form["claimed"]
err := app.ClaimWishes(claims, unclaims) err := app.ClaimWishes(claims, unclaims)
if err != nil { 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": case "complete":
claims := form["claimed"] claims := r.Form["claimed"]
err := app.CompleteWishes(claims) err := app.CompleteWishes(claims)
if err != nil { 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": case "add":
wishName := form.Get("gift_name") wishName := r.Form.Get("gift_name")
if wishName == "" { if wishName == "" {
return response.Error(http.StatusBadRequest, "Gift name not provided") return response.Error(http.StatusBadRequest, "Gift name not provided")
} }
err := app.SuggestWishForUser(userReference, wishName) err := app.SuggestWishForUser(userReference, wishName)
if err != nil { 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": case "delete":
claims := form["unclaimed"] claims := r.Form["unclaimed"]
unclaims := form["claimed"] unclaims := r.Form["claimed"]
gifts := append(claims, unclaims...) gifts := append(claims, unclaims...)
err := app.RecindWishesForUser(gifts...) err := app.RecindWishesForUser(gifts...)
if err != nil { 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: default:
return response.Error(http.StatusBadRequest, "Invalid intent %q", intent) return response.Error(http.StatusBadRequest, "Invalid intent %q", intent)
} }
return response.SeeOther("/list/" + userReference) return rsvp.SeeOther("/list/" + userReference)
} }

View File

@ -2,8 +2,7 @@ package templates
import ( import (
"fmt" "fmt"
"io" "html/template"
"text/template"
) )
type InputProps struct { type InputProps struct {
@ -30,17 +29,13 @@ func (p *InputProps) Validate() bool {
return true 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 { func init() {
err := tmpls[name].Execute(w, data) Templates = load()
if err != nil {
return fmt.Errorf("Failed to execute '%s' template: %w\n", name, err)
}
return nil
} }
func loadTemplates() map[string]*template.Template { func load() map[string]*template.Template {
homeTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/home.gotmpl")) homeTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/home.gotmpl"))
loginTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/login.gotmpl")) loginTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/login.gotmpl"))
registerTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/register.gotmpl")) registerTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/register.gotmpl"))