Send GM status update email on every attendance change

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>
This commit is contained in:
Harry Culpan 2026-06-30 19:07:51 -04:00
parent 3426d5ad3d
commit 01d2cae282
3 changed files with 67 additions and 13 deletions

View file

@ -87,12 +87,12 @@ will attend the next session.`,
player.Attending = attending player.Attending = attending
log.Default().Printf("Received player response for %s: %s", player.Name, player.Attending) log.Default().Printf("Received player response for %s: %s", player.Name, player.Attending)
if !player.Gm { if !player.Gm {
if err := sendEventNotice( gmEmail := session.GMEmail()
session, if gmEmail != "" {
"Received player response", subject, body := session.FormatStatusUpdate(player.Name, attending, "email")
fmt.Sprintf(`Player %s has responded with %q`, player.Name, player.Attending), if err := sendNotice(gmEmail, subject, body); err != nil {
); err != nil { log.Default().Printf("ERROR sending GM notification: %s", err)
log.Default().Printf("ERROR: %s", err) }
} }
} }
} else { } else {

View file

@ -12,6 +12,7 @@ import (
"github.com/golang-jwt/jwt/v4" "github.com/golang-jwt/jwt/v4"
"github.com/hculpan/areweplaying/cmd/web/templates" "github.com/hculpan/areweplaying/cmd/web/templates"
"github.com/hculpan/areweplaying/pkg/data" "github.com/hculpan/areweplaying/pkg/data"
"github.com/hculpan/areweplaying/pkg/email"
) )
func routes(r *chi.Mux) { func routes(r *chi.Mux) {
@ -145,23 +146,32 @@ func GenerateJWTAndStoreInCookie(w http.ResponseWriter, key []byte) error {
func updatePlayerAttending(name, attending string, session data.Session) error { func updatePlayerAttending(name, attending string, session data.Session) error {
for i, p := range session.Players { for i, p := range session.Players {
if p.Name == name { if p.Name == name {
var newAttending string
switch strings.ToLower(attending) { switch strings.ToLower(attending) {
case "yes", "no": case "yes", "no":
session.Players[i].Attending = strings.ToLower(attending) newAttending = strings.ToLower(attending)
return data.PersistSession(session)
case "toggle": case "toggle":
newAttending := session.Players[i].Attending switch session.Players[i].Attending {
switch newAttending {
case "unknown", "no": case "unknown", "no":
newAttending = "yes" newAttending = "yes"
case "yes": default:
newAttending = "no" newAttending = "no"
} }
session.Players[i].Attending = newAttending
return data.PersistSession(session)
default: default:
return fmt.Errorf("unrecognized attending indicator %q", attending) 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
} }
} }

View file

@ -1,5 +1,10 @@
package data package data
import (
"fmt"
"strings"
)
const ( const (
DATE_FORMAT string = "Monday Jan 2, 2006 3:04pm" DATE_FORMAT string = "Monday Jan 2, 2006 3:04pm"
) )
@ -20,6 +25,45 @@ type Session struct {
Password string `json:"password"` Password string `json:"password"`
} }
func (s *Session) GMEmail() string {
for _, p := range s.Players {
if p.Gm {
return p.Email
}
}
return ""
}
func (s *Session) FormatStatusUpdate(playerName, newStatus, source string) (string, string) {
subject := fmt.Sprintf("Player Status Update: %s → %s", playerName, newStatus)
var yes, no, unknown int
for _, p := range s.Players {
switch p.Attending {
case "yes":
yes++
case "no":
no++
default:
unknown++
}
}
var sb strings.Builder
fmt.Fprintf(&sb, "%s has updated their status to %q (via %s).\n\n", playerName, newStatus, source)
fmt.Fprintf(&sb, "Attending: %d | Not attending: %d | No response: %d\n\n", yes, no, unknown)
sb.WriteString("Current player status:\n")
for _, p := range s.Players {
role := ""
if p.Gm {
role = " (GM)"
}
fmt.Fprintf(&sb, " %s%s: %s\n", p.Name, role, p.Attending)
}
return subject, sb.String()
}
func (s *Session) GetPlayerByEmail(addr string) *Player { func (s *Session) GetPlayerByEmail(addr string) *Player {
for i, p := range s.Players { for i, p := range s.Players {
if addr == p.Email { if addr == p.Email {