diff --git a/internal/api/handler.go b/internal/api/handler.go new file mode 100644 index 0000000..53fcf14 --- /dev/null +++ b/internal/api/handler.go @@ -0,0 +1,41 @@ +package api + +import ( + "encoding/xml" + "net/http" + + "github.com/Kichiyaki/lubimyczytacrss/internal/lubimyczytac" + "github.com/go-chi/chi/v5" +) + +type Handler struct { + client *lubimyczytac.Client +} + +func NewHandler(client *lubimyczytac.Client) *Handler { + return &Handler{client: client} +} + +func (h *Handler) Register(r chi.Router) { + r.Get("/api/v1/rss/author/{authorID}", h.getRSSAuthor) +} + +func (h *Handler) getRSSAuthor(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + author, err := h.client.GetAuthor(ctx, chi.URLParamFromCtx(ctx, "authorID")) + if err == lubimyczytac.ErrAuthorNotFound { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`author not found`)) + return + } + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(`something went wrong while getting author info: ` + err.Error())) + return + } + + w.Header().Set("Content-Type", "text/xml; charset=utf-8") + w.WriteHeader(http.StatusOK) + _ = xml.NewEncoder(w).Encode(rssMainFromAuthor(author)) +} diff --git a/internal/api/rss.go b/internal/api/rss.go new file mode 100644 index 0000000..114c930 --- /dev/null +++ b/internal/api/rss.go @@ -0,0 +1,59 @@ +package api + +import ( + "encoding/xml" + + "github.com/Kichiyaki/lubimyczytacrss/internal/lubimyczytac" +) + +type rssItem struct { + XMLName xml.Name `xml:"item"` + Title string `xml:"title"` + Link string `xml:"link"` + GUID string `xml:"guid"` + Description string `xml:"description"` +} + +func rssItemsFromBooks(books []lubimyczytac.Book) []rssItem { + items := make([]rssItem, len(books)) + for i, b := range books { + items[i] = rssItem{ + Title: b.Title, + Link: b.URL, + GUID: b.URL, + Description: "", + } + } + return items +} + +type rssChannel struct { + XMLName xml.Name `xml:"channel"` + Title string `xml:"title"` + Link string `xml:"link"` + Description string `xml:"description"` + Language string `xml:"language"` + Items []rssItem `xml:"items"` +} + +func rssChannelFromAuthor(author lubimyczytac.Author) rssChannel { + return rssChannel{ + Title: author.Name, + Description: author.ShortDescription, + Link: author.URL, + Items: rssItemsFromBooks(author.Books), + } +} + +type rssMain struct { + XMLName xml.Name `xml:"rss"` + Version string `xml:"version,attr"` + Channel rssChannel `xml:"channel"` +} + +func rssMainFromAuthor(author lubimyczytac.Author) rssMain { + return rssMain{ + Version: "2.0", + Channel: rssChannelFromAuthor(author), + } +} diff --git a/main.go b/main.go index a1dafe0..1a3f38b 100644 --- a/main.go +++ b/main.go @@ -2,13 +2,14 @@ package main import ( "context" - "encoding/xml" "log" "net/http" "os" "os/signal" "time" + "github.com/Kichiyaki/lubimyczytacrss/internal/api" + "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/v5" @@ -21,28 +22,9 @@ const ( ) func main() { - r := chi.NewRouter() - r.Use( - middleware.RealIP, - middleware.RequestLogger(&middleware.DefaultLogFormatter{ - NoColor: true, - Logger: log.Default(), - }), - middleware.Recoverer, - middleware.Heartbeat("/health"), - ) - newHandler(lubimyczytac.NewClient(&http.Client{ + httpSrv := newServer(newRouter(lubimyczytac.NewClient(&http.Client{ Timeout: defaultClientTimeout, - })).register(r) - - httpSrv := &http.Server{ - Addr: ":9234", - Handler: r, - ReadTimeout: 2 * time.Second, - ReadHeaderTimeout: 2 * time.Second, - WriteTimeout: 2 * time.Second, - IdleTimeout: 2 * time.Second, - } + }))) go func(httpSrv *http.Server) { if err := httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed { @@ -64,86 +46,28 @@ func main() { } } -type rssItem struct { - XMLName xml.Name `xml:"item"` - Title string `xml:"title"` - Link string `xml:"link"` - GUID string `xml:"guid"` - Description string `xml:"description"` -} - -func rssItemsFromBooks(books []lubimyczytac.Book) []rssItem { - items := make([]rssItem, len(books)) - for i, b := range books { - items[i] = rssItem{ - Title: b.Title, - Link: b.URL, - GUID: b.URL, - Description: "", - } - } - return items -} - -type rssChannel struct { - XMLName xml.Name `xml:"channel"` - Title string `xml:"title"` - Link string `xml:"link"` - Description string `xml:"description"` - Language string `xml:"language"` - Items []rssItem `xml:"items"` -} - -func rssChannelFromAuthor(author lubimyczytac.Author) rssChannel { - return rssChannel{ - Title: author.Name, - Description: author.ShortDescription, - Link: author.URL, - Items: rssItemsFromBooks(author.Books), +func newServer(h http.Handler) *http.Server { + return &http.Server{ + Addr: ":9234", + Handler: h, + ReadTimeout: 2 * time.Second, + ReadHeaderTimeout: 2 * time.Second, + WriteTimeout: 2 * time.Second, + IdleTimeout: 2 * time.Second, } } -type rssMain struct { - XMLName xml.Name `xml:"rss"` - Version string `xml:"version,attr"` - Channel rssChannel `xml:"channel"` -} - -func rssMainFromAuthor(author lubimyczytac.Author) rssMain { - return rssMain{ - Version: "2.0", - Channel: rssChannelFromAuthor(author), - } -} - -type handler struct { - client *lubimyczytac.Client -} - -func newHandler(client *lubimyczytac.Client) *handler { - return &handler{client: client} -} - -func (h *handler) register(r chi.Router) { - r.Get("/api/v1/rss/author/{authorID}", h.getRSSAuthor) -} - -func (h *handler) getRSSAuthor(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - author, err := h.client.GetAuthor(ctx, chi.URLParamFromCtx(ctx, "authorID")) - if err == lubimyczytac.ErrAuthorNotFound { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`author not found`)) - return - } - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte(`something went wrong while getting author info: ` + err.Error())) - return - } - - w.Header().Set("Content-Type", "text/xml; charset=utf-8") - w.WriteHeader(http.StatusOK) - _ = xml.NewEncoder(w).Encode(rssMainFromAuthor(author)) +func newRouter(client *lubimyczytac.Client) *chi.Mux { + r := chi.NewRouter() + r.Use( + middleware.RealIP, + middleware.RequestLogger(&middleware.DefaultLogFormatter{ + NoColor: true, + Logger: log.Default(), + }), + middleware.Recoverer, + middleware.Heartbeat("/health"), + ) + api.NewHandler(client).Register(r) + return r }