66 "log/slog"
77 "net/http"
88
9- "github.com/bsv-blockchain/go-wallet-toolbox/pkg/defs"
109 "github.com/bsv-blockchain/go-wallet-toolbox/pkg/internal/logging"
1110 servercommon "github.com/bsv-blockchain/go-wallet-toolbox/pkg/internal/server"
1211 "github.com/bsv-blockchain/go-wallet-toolbox/pkg/services/chaintracks/models"
@@ -16,28 +15,31 @@ import (
1615// Handler implements the HTTP API endpoints for Chaintracks services, including routing, logging, and config access.
1716// It embeds an HTTP multiplexer, logger, and validated service configuration for BSV network operations.
1817type Handler struct {
19- logger * slog.Logger
20- mux * http.ServeMux
21- config defs. ChaintracksServiceConfig
18+ logger * slog.Logger
19+ mux * http.ServeMux
20+ service * Service
2221}
2322
2423// NewHandler creates a new Handler with the provided logger and ChaintracksServiceConfig.
2524// NewHandler validates the config and registers HTTP handlers for root, robots.txt, and getChain endpoints.
2625// Returns an initialized Handler or an error if validation fails.
27- func NewHandler (logger * slog.Logger , config defs.ChaintracksServiceConfig ) (* Handler , error ) {
28- if err := config .Validate (); err != nil {
29- return nil , fmt .Errorf ("invalid chaintracks service config: %w" , err )
30- }
31-
26+ func NewHandler (logger * slog.Logger , service * Service ) (* Handler , error ) {
3227 handler := & Handler {
33- logger : logging .Child (logger , "chaintracks_handler" ),
34- mux : http .NewServeMux (),
35- config : config ,
28+ logger : logging .Child (logger , "chaintracks_handler" ),
29+ mux : http .NewServeMux (),
30+ service : service ,
3631 }
3732
38- handler .mux .HandleFunc ("/robots.txt" , handler .handleRobotsTxt )
39- handler .mux .HandleFunc ("/" , handler .handleRoot )
40- handler .mux .HandleFunc ("/getChain" , handler .handleGetChain )
33+ handler .mux .HandleFunc ("GET /robots.txt" , handler .handleRobotsTxt )
34+ handler .mux .HandleFunc ("GET /" , handler .handleRoot )
35+ handler .mux .HandleFunc ("GET /getChain" , handler .handleGetChain )
36+ handler .mux .HandleFunc ("GET /getInfo" , handler .handleGetInfo )
37+ handler .mux .HandleFunc ("GET /getPresentHeight" , handler .handlePresentHeight )
38+ handler .mux .HandleFunc ("GET /findChainTipHashHex" , handler .handleFindTipHashHex )
39+ handler .mux .HandleFunc ("GET /findHeaderHexForHeight" , handler .handleFindHeaderHexForHeight )
40+
41+ // FIXME: in TS the endpoint is named findChainTipHeaderHex but it returns full JSON, not the hex
42+ handler .mux .HandleFunc ("GET /findChainTipHeaderHex" , handler .handleFindChainTipHeader )
4143
4244 return handler , nil
4345}
@@ -56,7 +58,7 @@ func (h *Handler) handleRobotsTxt(w http.ResponseWriter, r *http.Request) {
5658
5759func (h * Handler ) handleRoot (w http.ResponseWriter , r * http.Request ) {
5860 w .Header ().Set ("Content-Type" , "text/plain" )
59- if _ , err := fmt .Fprintf (w , "Chaintracks %sNet Block Header Service" , string (h .config . Chain )); err != nil {
61+ if _ , err := fmt .Fprintf (w , "Chaintracks %sNet Block Header Service" , string (h .service . GetChain () )); err != nil {
6062 h .logger .Error ("failed to write root response" , slog .String ("error" , err .Error ()))
6163 }
6264}
@@ -65,7 +67,109 @@ func (h *Handler) handleGetChain(w http.ResponseWriter, r *http.Request) {
6567 w .Header ().Set ("Content-Type" , "application/json" )
6668
6769 response := models.ResponseFrame [string ]{
68- Value : to .Ptr (string (h .config .Chain )),
70+ Value : to .Ptr (string (h .service .GetChain ())),
71+ Status : models .ResponseStatusSuccess ,
72+ }
73+
74+ h .writeJSONResponse (w , http .StatusOK , response )
75+ }
76+
77+ func (h * Handler ) handleGetInfo (w http.ResponseWriter , r * http.Request ) {
78+ w .Header ().Set ("Content-Type" , "application/json" )
79+
80+ info , err := h .service .GetInfo (r .Context ())
81+ if err != nil {
82+ h .logger .Error ("failed to get info" , slog .String ("error" , err .Error ()))
83+ http .Error (w , "Internal Server Error" , http .StatusInternalServerError )
84+ return
85+ }
86+
87+ response := models.ResponseFrame [models.InfoResponse ]{
88+ Value : info ,
89+ Status : models .ResponseStatusSuccess ,
90+ }
91+
92+ h .writeJSONResponse (w , http .StatusOK , response )
93+ }
94+
95+ func (h * Handler ) handlePresentHeight (w http.ResponseWriter , r * http.Request ) {
96+ w .Header ().Set ("Content-Type" , "application/json" )
97+
98+ height , err := h .service .GetPresentHeight (r .Context ())
99+ if err != nil {
100+ h .logger .Error ("failed to get present height" , slog .String ("error" , err .Error ()))
101+ http .Error (w , "Internal Server Error" , http .StatusInternalServerError )
102+ return
103+ }
104+
105+ response := models.ResponseFrame [uint ]{
106+ Value : to .Ptr (height ),
107+ Status : models .ResponseStatusSuccess ,
108+ }
109+
110+ h .writeJSONResponse (w , http .StatusOK , response )
111+ }
112+
113+ func (h * Handler ) handleFindChainTipHeader (w http.ResponseWriter , r * http.Request ) {
114+ w .Header ().Set ("Content-Type" , "application/json" )
115+
116+ tipHeader , err := h .service .FindChainTipHeader (r .Context ())
117+ if err != nil {
118+ h .logger .Error ("failed to find chain tip header hex" , slog .String ("error" , err .Error ()))
119+ http .Error (w , "Internal Server Error" , http .StatusInternalServerError )
120+ return
121+ }
122+
123+ response := models.ResponseFrame [models.BlockHeader ]{
124+ Value : liveBlockHeaderToBlockHeaderDTO (tipHeader ),
125+ Status : models .ResponseStatusSuccess ,
126+ }
127+
128+ h .writeJSONResponse (w , http .StatusOK , response )
129+ }
130+
131+ func (h * Handler ) handleFindTipHashHex (w http.ResponseWriter , r * http.Request ) {
132+ w .Header ().Set ("Content-Type" , "application/json" )
133+
134+ tipHash , err := h .service .FindChainTipHeader (r .Context ())
135+ if err != nil {
136+ h .logger .Error ("failed to find chain tip hash hex" , slog .String ("error" , err .Error ()))
137+ http .Error (w , "Internal Server Error" , http .StatusInternalServerError )
138+ return
139+ }
140+
141+ response := models.ResponseFrame [string ]{
142+ Value : to .Ptr (tipHash .Hash ),
143+ Status : models .ResponseStatusSuccess ,
144+ }
145+
146+ h .writeJSONResponse (w , http .StatusOK , response )
147+ }
148+
149+ func (h * Handler ) handleFindHeaderHexForHeight (w http.ResponseWriter , r * http.Request ) {
150+ w .Header ().Set ("Content-Type" , "application/json" )
151+
152+ heightParam := r .URL .Query ().Get ("height" )
153+ if heightParam == "" {
154+ http .Error (w , "Missing 'height' query parameter" , http .StatusBadRequest )
155+ return
156+ }
157+
158+ var height uint
159+ if _ , err := fmt .Sscanf (heightParam , "%d" , & height ); err != nil {
160+ http .Error (w , "Invalid 'height' query parameter" , http .StatusBadRequest )
161+ return
162+ }
163+
164+ header , err := h .service .FindHeaderForHeight (r .Context (), height )
165+ if err != nil {
166+ h .logger .Error ("failed to find header hex for height" , slog .String ("error" , err .Error ()))
167+ http .Error (w , "Internal Server Error" , http .StatusInternalServerError )
168+ return
169+ }
170+
171+ response := models.ResponseFrame [models.BlockHeader ]{
172+ Value : liveBlockHeaderToBlockHeaderDTO (header ),
69173 Status : models .ResponseStatusSuccess ,
70174 }
71175
0 commit comments