package http import ( "context" "encoding/json" "fmt" "io/ioutil" "net/http" "time" bookish "github.com/davidlick/bookish/bookish-server" "github.com/davidlick/bookish/bookish-server/rental" "github.com/davidlick/bookish/bookish-server/renter" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "github.com/sirupsen/logrus" ) // Server holds dependencies of the API. type Server struct { Port string BooksHost string Logger *logrus.Logger Renters renter.Service Rentals rental.Service // LibraryMap holds a map of values that are returned by the books API. This is used to check // if a book exists in the library. LibraryMap struct { UpdatedTime time.Time Map map[string]bookish.Book } server *http.Server } // Run configures and starts the server. func (s *Server) Run() error { s.server = &http.Server{ Addr: ":" + s.Port, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, Handler: s.BuildRoutes(), } s.Logger.Info("server launched on port: " + s.Port) return s.server.ListenAndServe() } // Shutdown attempts to gracefully shutdown the server. func (s *Server) Shutdown(ctx context.Context) error { return s.server.Shutdown(ctx) } // UpdateMap will query the books API if 1 hour has passed since the data was last refreshed. func (s *Server) UpdateMap() error { // If it has not been at least an hour since the last refresh we do not need to refresh. if !time.Now().After(s.LibraryMap.UpdatedTime.Add(1 * time.Hour)) { return nil } resp, err := http.Get(fmt.Sprintf("%s/api/v1/books", s.BooksHost)) if err != nil { return err } respBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return err } var books []bookish.Book err = json.Unmarshal(respBytes, &books) if err != nil { return err } // Empty the existing map. s.LibraryMap.Map = map[string]bookish.Book{} for _, book := range books { s.LibraryMap.Map[book.Title] = book } s.LibraryMap.UpdatedTime = time.Now() return nil } // BuildRoutes builds the API endpoints and connects their handlers. func (s *Server) BuildRoutes() http.Handler { r := chi.NewRouter() r.Use(cors) r.Use(middleware.RequestID) r.Use(middleware.RealIP) r.Use(middleware.Logger) r.Use(middleware.Recoverer) r.Group(func(r chi.Router) { r.Route("/api", func(r chi.Router) { r.Route("/v1", func(r chi.Router) { r.Route("/renters", func(r chi.Router) { r.Post("/", s.registerRenter) r.Get("/", s.listRenters) r.Route("/{renterId}", func(r chi.Router) { r.Use(s.renterCtx) r.Get("/", s.fetchRenter) r.Route("/books", func(r chi.Router) { r.Use(s.bookInLibrary) r.Post("/checkout", s.checkoutBook) r.Post("/return", s.returnBook) }) }) }) }) }) }) return r }