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
log.Default().Printf("Received player response for %s: %s", player.Name, player.Attending)
if !player.Gm {
if err := sendEventNotice(
session,
"Received player response",
fmt.Sprintf(`Player %s has responded with %q`, player.Name, player.Attending),
); err != nil {
log.Default().Printf("ERROR: %s", err)
gmEmail := session.GMEmail()
if gmEmail != "" {
subject, body := session.FormatStatusUpdate(player.Name, attending, "email")
if err := sendNotice(gmEmail, subject, body); err != nil {
log.Default().Printf("ERROR sending GM notification: %s", err)
}
}
}
} else {

View file

@ -12,6 +12,7 @@ import (
"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) {
@ -145,23 +146,32 @@ func GenerateJWTAndStoreInCookie(w http.ResponseWriter, key []byte) error {
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":
session.Players[i].Attending = strings.ToLower(attending)
return data.PersistSession(session)
newAttending = strings.ToLower(attending)
case "toggle":
newAttending := session.Players[i].Attending
switch newAttending {
switch session.Players[i].Attending {
case "unknown", "no":
newAttending = "yes"
case "yes":
default:
newAttending = "no"
}
session.Players[i].Attending = newAttending
return data.PersistSession(session)
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
}
}

View file

@ -1,5 +1,10 @@
package data
import (
"fmt"
"strings"
)
const (
DATE_FORMAT string = "Monday Jan 2, 2006 3:04pm"
)
@ -20,6 +25,45 @@ type Session struct {
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 {
for i, p := range s.Players {
if addr == p.Email {