Add dynamic OG image generation for rich iMessage/social previews

Generates a branded 1200x630 PNG per event with title, date/time/location
using Go's image package and a custom bitmap font. Supports BBQ_BASE_URL
env var for absolute URLs required by link preview crawlers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 15:50:24 -04:00
parent ef3aa3e9c3
commit 6a70135a5d
4 changed files with 241 additions and 5 deletions
+10 -4
View File
@@ -32,18 +32,20 @@ var schemaSQL string
var pageTmpl map[string]*template.Template
type Server struct {
q *db.Queries
db *sql.DB
q *db.Queries
db *sql.DB
baseURL string
// SSE: map of event slug -> set of channels
mu sync.Mutex
clients map[string]map[chan struct{}]struct{}
}
func NewServer(database *sql.DB) *Server {
func NewServer(database *sql.DB, baseURL string) *Server {
return &Server{
q: db.New(database),
db: database,
baseURL: baseURL,
clients: make(map[string]map[chan struct{}]struct{}),
}
}
@@ -90,6 +92,7 @@ func main() {
if v := os.Getenv("PORT"); v != "" {
port = v
}
baseURL := os.Getenv("BBQ_BASE_URL") // e.g. https://bbq.torrtle.co
database, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&_foreign_keys=on")
if err != nil {
@@ -128,7 +131,7 @@ func main() {
template.New("").Funcs(funcMap).ParseFS(templateFS, "templates/slots.html"),
)
srv := NewServer(database)
srv := NewServer(database, baseURL)
r := chi.NewRouter()
r.Use(middleware.Logger)
@@ -149,6 +152,9 @@ func main() {
r.Post("/e/{slug}/rsvp", srv.handleRsvp)
r.Delete("/e/{slug}/rsvp/{rsvpID}", srv.handleUnrsvp)
// OG image
r.Get("/e/{slug}/og.png", srv.handleOGImage)
// SSE
r.Get("/e/{slug}/sse", srv.handleSSE)