Files
weatherapi/internal/adapters/httpapi/server.go
Eric Rakestraw 27817f9e43
All checks were successful
ci/woodpecker/push/build-image Pipeline was successful
Initial MVP commit
2026-03-17 08:35:16 -05:00

111 lines
3.1 KiB
Go

package httpapi
import (
"context"
"encoding/json"
"net/http"
"time"
"gitea.maximumdirect.net/ejr/weatherapi/internal/application/alerts"
"gitea.maximumdirect.net/ejr/weatherapi/internal/application/forecasts"
"gitea.maximumdirect.net/ejr/weatherapi/internal/application/observations"
"gitea.maximumdirect.net/ejr/weatherapi/internal/platform/timeparse"
)
type ObservationService interface {
GetCurrent(ctx context.Context) (observations.CurrentResponse, error)
}
type ForecastService interface {
GetByTimestamp(ctx context.Context, ts time.Time) (forecasts.Response, error)
}
type AlertService interface {
GetCurrent(ctx context.Context) (alerts.Response, error)
}
type Server struct {
obsSvc ObservationService
fcSvc ForecastService
alertsSvc AlertService
}
func NewServer(obsSvc ObservationService, fcSvc ForecastService, alertsSvc AlertService) *Server {
return &Server{obsSvc: obsSvc, fcSvc: fcSvc, alertsSvc: alertsSvc}
}
func (s *Server) Handler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/observations/current", s.handleObservationsCurrent)
mux.HandleFunc("/forecast", s.handleForecast)
mux.HandleFunc("/alerts/current", s.handleAlertsCurrent)
return mux
}
func (s *Server) handleObservationsCurrent(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "only GET is supported")
return
}
resp, err := s.obsSvc.GetCurrent(r.Context())
if err != nil {
writeError(w, http.StatusInternalServerError, "internal_error", "failed to fetch current observations")
return
}
writeJSON(w, http.StatusOK, resp)
}
func (s *Server) handleForecast(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "only GET is supported")
return
}
raw := r.URL.Query().Get("timestamp")
ts, err := timeparse.ParseTimestamp(raw)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid_request", err.Error())
return
}
resp, err := s.fcSvc.GetByTimestamp(r.Context(), ts)
if err != nil {
writeError(w, http.StatusInternalServerError, "internal_error", "failed to fetch forecast")
return
}
writeJSON(w, http.StatusOK, resp)
}
func (s *Server) handleAlertsCurrent(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "only GET is supported")
return
}
resp, err := s.alertsSvc.GetCurrent(r.Context())
if err != nil {
writeError(w, http.StatusInternalServerError, "internal_error", "failed to fetch current alerts")
return
}
writeJSON(w, http.StatusOK, resp)
}
type errorResponse struct {
Code string `json:"code"`
Message string `json:"message"`
}
func writeError(w http.ResponseWriter, status int, code, message string) {
writeJSON(w, status, errorResponse{Code: code, Message: message})
}
func writeJSON(w http.ResponseWriter, status int, payload any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(payload)
}