package server import ( "encoding/json" "errors" "fmt" "net" "net/http" "github.com/charmbracelet/log" ) type WebServer struct { server *Server listener net.Listener logger *log.Logger httpServer *http.Server } func NewWebServer(server *Server) (*WebServer, error) { listener, err := net.Listen("tcp", fmt.Sprintf(":%d", server.Config.Web.Port)) if err != nil { return nil, fmt.Errorf("cannot create listener: %w", err) } ws := &WebServer{ server: server, listener: listener, logger: server.Logger, } mux := http.NewServeMux() mux.HandleFunc("/health", ws.healthHandler) mux.HandleFunc("/next_birthdays", ws.nextBirthdayHandler) ws.httpServer = &http.Server{Handler: mux} return ws, nil } func (ws *WebServer) Start() error { ws.logger.Debug("starting web server") go func() { if err := ws.httpServer.Serve(ws.listener); err != nil && !errors.Is(err, http.ErrServerClosed) { ws.logger.Fatal("cannot start web server", "error", err) } }() return nil } func (ws *WebServer) Stop() error { ws.logger.Debug("stopping web server") if err := ws.httpServer.Close(); err != nil { return fmt.Errorf("cannot stop web server: %w", err) } return nil } func (ws *WebServer) Port() int { return ws.listener.Addr().(*net.TCPAddr).Port } func (ws *WebServer) healthHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "OK") } func (ws *WebServer) nextBirthdayHandler(w http.ResponseWriter, r *http.Request) { ws.JSON(w, http.StatusOK, ws.server.NextBirthdays()) } func (ws *WebServer) JSON(w http.ResponseWriter, statusCode int, data any) { b, err := json.Marshal(data) if err != nil { ws.logger.Error("cannot marshal data", "error", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } w.WriteHeader(statusCode) if _, err := w.Write(b); err != nil { ws.logger.Error("cannot write to response writer", "error", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } }