Something went wrong. Try again.
An event companion and onboarding experience for the ATmosphere (alpha) atmo.quest
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158package main
import ( "atmoquest/config" "atmoquest/internal/admincrypto" "atmoquest/internal/db" "atmoquest/internal/oauthclient" "atmoquest/internal/oauthstore" "atmoquest/internal/session" "atmoquest/internal/users" "atmoquest/nats" "atmoquest/router" "context" "errors" "fmt" "log/slog" "net" "net/http" "os" "os/signal" "strings" "time"
"github.com/bluesky-social/indigo/atproto/syntax" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/httplog/v3" "golang.org/x/sync/errgroup")
func main() { ctx := context.Background() if err := run(ctx); err != nil { slog.Error("server failure", "error", err) os.Exit(1) }}
func run(ctx context.Context) error { ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) defer cancel()
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: config.Global.LogLevel, })) slog.SetDefault(logger)
if err := config.Global.Validate(); err != nil { return fmt.Errorf("invalid config: %w", err) }
conn, err := db.Open(config.Global.DatabaseURL) if err != nil { return fmt.Errorf("open db: %w", err) } defer conn.Close() if err := db.Migrate(conn); err != nil { return fmt.Errorf("migrate db: %w", err) } slog.Info("db ready", "dsn", config.Global.DatabaseURL)
for _, s := range strings.Fields(os.Getenv("BOOTSTRAP_ADMIN_DIDS")) { did, err := syntax.ParseDID(s) if err != nil { slog.Warn("bootstrap admin: invalid DID, skipping", "value", s, "error", err) continue } if err := users.SetAdmin(ctx, conn, did, true); err != nil { if errors.Is(err, users.ErrNotFound) { slog.Warn("bootstrap admin: DID hasn't signed in yet, skipping", "did", did) continue } return fmt.Errorf("bootstrap admin %s: %w", did, err) } slog.Info("bootstrap admin: granted", "did", did) }
r := chi.NewMux() r.Use( httplog.RequestLogger(logger, nil), middleware.Recoverer, )
sessionMgr, err := session.New(config.Global) if err != nil { return fmt.Errorf("session manager: %w", err) }
oauthApp, oauthClientID, err := oauthclient.Build(config.Global, oauthstore.New(conn)) if err != nil { return fmt.Errorf("build oauth client: %w", err) } slog.Info("oauth client ready", "client_id", oauthClientID, "localhost", config.Global.IsLocalhost())
// Admin signing key — used to sign event badge designs. In dev we let the // loader generate one and write it to data/admin_signing.key; in prod // the operator must set ADMIN_SIGNING_KEY (base64 ed25519 seed). signer, err := admincrypto.Load(config.Global.Environment == config.Dev) if err != nil { return fmt.Errorf("load admin signer: %w", err) } slog.Info("admin signer ready", "key_id", signer.KeyID)
ns, err := nats.SetupNATS(ctx) if err != nil { return err }
eg, egctx := errgroup.WithContext(ctx)
if err := router.SetupRoutes(egctx, r, sessionMgr, oauthApp, ns, conn, signer); err != nil { return fmt.Errorf("error setting up routes: %w", err) }
addr := fmt.Sprintf("%s:%s", config.Global.Host, config.Global.Port) srv := &http.Server{ Addr: addr, Handler: r, BaseContext: func(l net.Listener) context.Context { return ctx }, ErrorLog: slog.NewLogLogger( slog.Default().Handler(), slog.LevelError, ), }
eg.Go(func() error { slog.Info("server started", "addr", srv.Addr) err := srv.ListenAndServe()
if err == nil || err == http.ErrServerClosed { return nil }
return fmt.Errorf("server error: %w", err) })
eg.Go(func() error { <-egctx.Done()
slog.Debug("shutdown signal received")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel()
slog.Debug("shutting down server...")
if err := srv.Shutdown(shutdownCtx); err != nil { return fmt.Errorf("server shutdown error: %w", err) }
return nil })
return eg.Wait()}