package httpapi import ( "net/http" "github.com/jackc/pgx/v5/pgxpool" "github.com/mitbringsl/backend/internal/auth" "github.com/mitbringsl/backend/internal/config" "github.com/mitbringsl/backend/internal/store" ) // API bundles all handler groups and wires the router. type API struct { cfg *config.Config pool *pgxpool.Pool health *HealthHandler auth *AuthHandler lists *ListHandler ops *OpsHandler suggest *SuggestHandler } // NewAPI constructs the API with all handler groups. func NewAPI(cfg *config.Config, pool *pgxpool.Pool) *API { // Auth stores users := auth.NewUserStore(pool) sessions := auth.NewSessionStore(pool, auth.SessionConfig{ TokenBytes: cfg.SessionTokenBytes, TTL: cfg.SessionTokenTTL, }) oidcSvc := auth.NewOIDCService(buildProviders(cfg)) if len(oidcSvc.Providers()) == 0 { oidcSvc = nil } // Sync stores listStore := store.NewListStore(pool) itemStore := store.NewItemStore(pool) opStore := store.NewOpStore(pool) suggestStore := store.NewSuggestStore(pool) return &API{ cfg: cfg, pool: pool, health: &HealthHandler{Pool: pool}, auth: NewAuthHandler(users, sessions, oidcSvc, cfg), lists: NewListHandler(listStore, itemStore), ops: NewOpsHandler(opStore, listStore), suggest: NewSuggestHandler(suggestStore), } } // buildProviders assembles the OIDC provider map from config. Only enabled // providers with a non-empty issuer and client_id are included. func buildProviders(cfg *config.Config) map[string]auth.ProviderConfig { p := map[string]auth.ProviderConfig{} if cfg.GoogleOIDC.Enabled && cfg.GoogleOIDC.Issuer != "" && cfg.GoogleOIDC.ClientID != "" { p["google"] = auth.ProviderConfig{Issuer: cfg.GoogleOIDC.Issuer, ClientID: cfg.GoogleOIDC.ClientID} } if cfg.GenericOIDC.Enabled && cfg.GenericOIDC.Issuer != "" && cfg.GenericOIDC.ClientID != "" { p["generic"] = auth.ProviderConfig{Issuer: cfg.GenericOIDC.Issuer, ClientID: cfg.GenericOIDC.ClientID} } return p } // Handler returns the fully wired http.Handler with all middleware applied. func (a *API) Handler() http.Handler { mux := http.NewServeMux() // --- public health endpoints (no auth) --- mux.HandleFunc("GET /healthz", a.health.Healthz) mux.HandleFunc("GET /readyz", a.health.Readyz) // --- auth endpoints --- mux.HandleFunc("POST /auth/register", a.auth.Register) mux.HandleFunc("POST /auth/login", a.auth.Login) mux.HandleFunc("POST /auth/oidc", a.auth.OIDC) mux.HandleFunc("POST /auth/logout", a.auth.Logout) // --- authenticated API endpoints --- mux.Handle("GET /api/lists", a.RequireAuth(http.HandlerFunc(a.lists.List))) mux.Handle("POST /api/lists", a.RequireAuth(http.HandlerFunc(a.lists.Create))) mux.Handle("POST /api/lists/join", a.RequireAuth(http.HandlerFunc(a.lists.Join))) mux.Handle("GET /api/lists/{id}", a.RequireAuth(http.HandlerFunc(a.lists.Get))) mux.Handle("POST /api/lists/{id}/invite", a.RequireAuth(http.HandlerFunc(a.lists.Invite))) mux.Handle("POST /api/lists/{id}/ops", a.RequireAuth(http.HandlerFunc(a.ops.Push))) mux.Handle("GET /api/lists/{id}/ops", a.RequireAuth(http.HandlerFunc(a.ops.Pull))) mux.Handle("GET /api/suggestions", a.RequireAuth(http.HandlerFunc(a.suggest.Search))) return Chain( mux, requestIDMiddleware, loggingMiddleware, recoverMiddleware, corsMiddleware(a.cfg), ) }