@@ -17,6 +17,7 @@ import ( |
| 17 | 17 | "syscall" |
| 18 | 18 | "time" |
| 19 | 19 | |
| 20 | + "github.com/tenseleyFlow/shithub/internal/infra/db" |
| 20 | 21 | "github.com/tenseleyFlow/shithub/internal/web/handlers" |
| 21 | 22 | ) |
| 22 | 23 | |
@@ -44,13 +45,31 @@ func Run(ctx context.Context, opts Options) error { |
| 44 | 45 | return fmt.Errorf("load logo: %w", err) |
| 45 | 46 | } |
| 46 | 47 | |
| 48 | + // DB pool is optional in S01: the server boots without one (the hello |
| 49 | + // page works), but /readyz reports 503 if a DB is configured but |
| 50 | + // unreachable. S02+ will make a pool effectively required. |
| 51 | + var pool *pgxpoolHandle |
| 52 | + if cfg := db.Defaults().Resolve(); cfg.URL != "" { |
| 53 | + p, err := db.Open(ctx, cfg) |
| 54 | + if err != nil { |
| 55 | + logger.Warn("db: open failed; /readyz will report unhealthy", "error", err) |
| 56 | + } else { |
| 57 | + pool = &pgxpoolHandle{p: p} |
| 58 | + defer p.Close() |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 47 | 62 | mux := http.NewServeMux() |
| 48 | | - if err := handlers.Register(mux, handlers.Deps{ |
| 63 | + deps := handlers.Deps{ |
| 49 | 64 | Logger: logger, |
| 50 | 65 | TemplatesFS: TemplatesFS(), |
| 51 | 66 | StaticFS: StaticFS(), |
| 52 | 67 | LogoSVG: string(logoBytes), |
| 53 | | - }); err != nil { |
| 68 | + } |
| 69 | + if pool != nil { |
| 70 | + deps.ReadyCheck = pool.healthcheck |
| 71 | + } |
| 72 | + if err := handlers.Register(mux, deps); err != nil { |
| 54 | 73 | return fmt.Errorf("register handlers: %w", err) |
| 55 | 74 | } |
| 56 | 75 | |
@@ -95,3 +114,23 @@ func Run(ctx context.Context, opts Options) error { |
| 95 | 114 | } |
| 96 | 115 | return nil |
| 97 | 116 | } |
| 117 | + |
| 118 | +// pgxpoolHandle is an internal wrapper that converts the pool into the |
| 119 | +// callback-shape the handlers package expects, without exposing pgx types |
| 120 | +// to internal/web/handlers. It also lets us pass a nil pool through cleanly. |
| 121 | +type pgxpoolHandle struct { |
| 122 | + p interface { |
| 123 | + Close() |
| 124 | + } |
| 125 | +} |
| 126 | + |
| 127 | +func (h *pgxpoolHandle) healthcheck(ctx context.Context) error { |
| 128 | + // Re-open the type via the db package's typed helper. |
| 129 | + type pinger interface { |
| 130 | + Ping(context.Context) error |
| 131 | + } |
| 132 | + if p, ok := h.p.(pinger); ok { |
| 133 | + return p.Ping(ctx) |
| 134 | + } |
| 135 | + return nil |
| 136 | +} |