Switch to phone auth via Twilio SMS and add feature flag system

Replace email-based auth (Resend) with phone-based auth (Twilio SMS).
Add BBQ_FEATURES env var for toggling features at deploy time — when
auth is disabled, no login routes are registered and the app works
as before.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 08:45:15 -04:00
parent b3203a7506
commit 471cc3ad8c
15 changed files with 820 additions and 21 deletions
+28 -10
View File
@@ -32,21 +32,23 @@ var schemaSQL string
var pageTmpl map[string]*template.Template
type Server struct {
q *db.Queries
db *sql.DB
baseURL string
q *db.Queries
db *sql.DB
baseURL string
features Features
// SSE: map of event slug -> set of channels
mu sync.Mutex
clients map[string]map[chan struct{}]struct{}
}
func NewServer(database *sql.DB, baseURL string) *Server {
func NewServer(database *sql.DB, baseURL string, features Features) *Server {
return &Server{
q: db.New(database),
db: database,
baseURL: baseURL,
clients: make(map[string]map[chan struct{}]struct{}),
q: db.New(database),
db: database,
baseURL: baseURL,
features: features,
clients: make(map[string]map[chan struct{}]struct{}),
}
}
@@ -93,6 +95,7 @@ func main() {
port = v
}
baseURL := os.Getenv("BBQ_BASE_URL") // e.g. https://bbq.torrtle.co
features := parseFeatures(os.Getenv("BBQ_FEATURES"))
database, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&_foreign_keys=on")
if err != nil {
@@ -121,7 +124,7 @@ func main() {
// Parse each page with layout + shared partials
pageTmpl = make(map[string]*template.Template)
shared := []string{"templates/layout.html", "templates/slots.html"}
for _, page := range []string{"home", "event"} {
for _, page := range []string{"home", "event", "login", "dashboard", "name"} {
files := append([]string{"templates/" + page + ".html"}, shared...)
pageTmpl[page] = template.Must(
template.New("").Funcs(funcMap).ParseFS(templateFS, files...),
@@ -132,15 +135,30 @@ func main() {
template.New("").Funcs(funcMap).ParseFS(templateFS, "templates/slots.html"),
)
srv := NewServer(database, baseURL)
srv := NewServer(database, baseURL, features)
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(srv.sessionMiddleware)
// Static files
r.Handle("/static/*", http.FileServer(http.FS(staticFS)))
// Auth (conditional on feature flag)
if features.Auth {
r.Get("/login", srv.handleLoginPage)
r.Post("/login", srv.handleLoginSubmit)
r.Post("/login/verify", srv.handleVerifyCode)
r.Post("/logout", srv.handleLogout)
r.Group(func(r chi.Router) {
r.Use(srv.requireAuth)
r.Get("/dashboard", srv.handleDashboard)
r.Get("/account/name", srv.handleNamePage)
r.Post("/account/name", srv.handleNameSubmit)
})
}
// Home / create event
r.Get("/", srv.handleHome)
r.Post("/events", srv.handleCreateEvent)