When a player's attendance is updated — either via email (awp-cli check) or via the web toggle — an email is sent to the GM with: - who changed, what they changed to, and how (email or the site) - a summary count of attending/not attending/no response - the full current player status list Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
179 lines
4.6 KiB
Go
179 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/golang-jwt/jwt/v4"
|
|
"github.com/hculpan/areweplaying/cmd/web/templates"
|
|
"github.com/hculpan/areweplaying/pkg/data"
|
|
"github.com/hculpan/areweplaying/pkg/email"
|
|
)
|
|
|
|
func routes(r *chi.Mux) {
|
|
r.Post("/save-setup", func(w http.ResponseWriter, r *http.Request) {
|
|
saveSetupHandler(w, r)
|
|
})
|
|
|
|
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
|
comp := templates.MainPage()
|
|
comp.Render(context.Background(), w)
|
|
})
|
|
|
|
r.Post("/setup", func(w http.ResponseWriter, r *http.Request) {
|
|
setupHandler(w, r)
|
|
})
|
|
|
|
r.Get("/setup", func(w http.ResponseWriter, r *http.Request) {
|
|
setupHandler(w, r)
|
|
})
|
|
|
|
r.Post("/send-email", func(w http.ResponseWriter, r *http.Request) {
|
|
sendEmailHandler(w, r)
|
|
})
|
|
|
|
r.Get("/login", func(w http.ResponseWriter, r *http.Request) {
|
|
if ValidateJWTFromCookie(r, appKey) {
|
|
http.Redirect(w, r, "/setup", http.StatusSeeOther)
|
|
return
|
|
}
|
|
comp := templates.Login()
|
|
comp.Render(context.Background(), w)
|
|
})
|
|
|
|
r.Get("/send-reminder", func(w http.ResponseWriter, r *http.Request) {
|
|
sendReminderHandler(w, r)
|
|
})
|
|
|
|
r.Post("/admin/players/add", func(w http.ResponseWriter, r *http.Request) {
|
|
addPlayerHandler(w, r)
|
|
})
|
|
|
|
r.Post("/admin/players/delete", func(w http.ResponseWriter, r *http.Request) {
|
|
deletePlayerHandler(w, r)
|
|
})
|
|
|
|
r.Post("/admin/players/edit", func(w http.ResponseWriter, r *http.Request) {
|
|
editPlayerHandler(w, r)
|
|
})
|
|
|
|
r.Get("/sessioninfo", func(w http.ResponseWriter, r *http.Request) {
|
|
session, dataErr := data.ReadSessionData()
|
|
if dataErr != nil {
|
|
http.Error(w, dataErr.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
playerName := r.URL.Query().Get("playerName")
|
|
playerAttending := r.URL.Query().Get("playerAttending")
|
|
|
|
if len(playerName) > 0 && len(playerAttending) > 0 {
|
|
if err := updatePlayerAttending(playerName, playerAttending, session); err != nil {
|
|
log.Default().Println(err.Error())
|
|
}
|
|
}
|
|
|
|
comp := templates.PageBody(session)
|
|
comp.Render(context.Background(), w)
|
|
})
|
|
|
|
}
|
|
|
|
func ValidateJWTFromCookie(r *http.Request, key []byte) bool {
|
|
// Retrieve the JWT token from the cookie
|
|
cookie, err := r.Cookie("jwt_token")
|
|
if err != nil {
|
|
// Cookie not found
|
|
return false
|
|
}
|
|
|
|
tokenString := cookie.Value
|
|
|
|
// Parse the token
|
|
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
|
// Validate the algorithm used for the token
|
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
return nil, nil
|
|
}
|
|
return key, nil
|
|
})
|
|
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// Check if token is valid
|
|
if _, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
|
// Here you can also add more checks on claims if needed
|
|
// For example, check the issuer, subject, or expiration
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func GenerateJWTAndStoreInCookie(w http.ResponseWriter, key []byte) error {
|
|
// Create a new token object, specifying signing method and the claims you would like it to contain.
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
|
"exp": time.Now().Add(90 * 24 * time.Hour).Unix(), // 90 days expiration
|
|
})
|
|
|
|
// Sign and get the complete encoded token as a string using the secret
|
|
tokenString, err := token.SignedString(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create a cookie
|
|
cookie := http.Cookie{
|
|
Name: "jwt_token",
|
|
Value: tokenString,
|
|
Expires: time.Now().Add(90 * 24 * time.Hour),
|
|
HttpOnly: true, // HttpOnly if you want to prevent access to the cookie from JavaScript
|
|
}
|
|
|
|
// Set the cookie
|
|
http.SetCookie(w, &cookie)
|
|
|
|
return nil
|
|
}
|
|
|
|
func updatePlayerAttending(name, attending string, session data.Session) error {
|
|
for i, p := range session.Players {
|
|
if p.Name == name {
|
|
var newAttending string
|
|
switch strings.ToLower(attending) {
|
|
case "yes", "no":
|
|
newAttending = strings.ToLower(attending)
|
|
case "toggle":
|
|
switch session.Players[i].Attending {
|
|
case "unknown", "no":
|
|
newAttending = "yes"
|
|
default:
|
|
newAttending = "no"
|
|
}
|
|
default:
|
|
return fmt.Errorf("unrecognized attending indicator %q", attending)
|
|
}
|
|
session.Players[i].Attending = newAttending
|
|
if err := data.PersistSession(session); err != nil {
|
|
return err
|
|
}
|
|
if gmEmail := session.GMEmail(); gmEmail != "" {
|
|
subject, body := session.FormatStatusUpdate(name, newAttending, "the site")
|
|
e := email.NewEmail([]string{gmEmail}, []string{}, gmEmail, subject, body)
|
|
if err := email.SendEmail(e); err != nil {
|
|
log.Default().Printf("ERROR sending GM notification: %s", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("player %q not found", name)
|
|
}
|