package main import ( "encoding/json" "errors" "net/http" "time" ) type API struct { gh *GitHub store *Store sched *Scheduler } func (a *API) Routes(mux *http.ServeMux) { mux.HandleFunc("GET /healthz", a.health) mux.HandleFunc("GET /api/search", a.search) mux.HandleFunc("GET /api/status", a.status) a.savedRoutes(mux) } // codedError carries a stable code that the UI translates. type codedError struct { code string msg string detail string } func (e *codedError) Error() string { if e.detail != "" { return e.msg + ": " + e.detail } return e.msg } // GET /api/search?q=&language=&stars=&maxstars=&minforks=&topic=&license= // &pushed=&created=&in=&archived=1&forks=1&goodfirst=1&sort=&order=&page=&per_page= func (a *API) search(w http.ResponseWriter, r *http.Request) { c, err := ParseCriteria(r.URL.Query()) if err != nil { writeErr(w, http.StatusBadRequest, err) return } res, err := a.gh.Search(r.Context(), c) if err != nil { status := http.StatusBadGateway var ce *codedError if errors.As(err, &ce) { switch ce.code { case "rate_limited": status = http.StatusTooManyRequests case "bad_query": status = http.StatusBadRequest } } writeErr(w, status, err) return } writeJSON(w, http.StatusOK, res) } // GET /api/status: token configured and last known search quota (footer). func (a *API) status(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]any{ "token": a.gh.HasToken(), "rate": a.gh.Rate(), "tz": zoneName(), }) } // zoneName is the server time zone used by the schedules (TZ). func zoneName() string { if name := time.Local.String(); name != "Local" { return name } name, _ := time.Now().Zone() return name } func (a *API) health(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("ok")) } func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) } // writeErr returns {"error": "...", "code": "...", "detail": "..."}; the UI // translates known codes and falls back to the English message. func writeErr(w http.ResponseWriter, status int, err error) { body := map[string]string{"error": err.Error()} var ce *codedError if errors.As(err, &ce) { body["code"] = ce.code if ce.detail != "" { body["detail"] = ce.detail } } writeJSON(w, status, body) }