package server import ( "github.com/nkanaev/yarr/storage" "github.com/mmcdole/gofeed" "net/http" "encoding/json" "os" "log" "io" "mime" "strings" "path/filepath" "strconv" "math" ) func IndexHandler(rw http.ResponseWriter, req *http.Request) { f, err := os.Open("template/index.html") if err != nil { log.Fatal(err) } defer f.Close() rw.Header().Set("Content-Type", "text/html") io.Copy(rw, f) } func StaticHandler(rw http.ResponseWriter, req *http.Request) { path := "template/static/" + Vars(req)["path"] f, err := os.Open(path) if err != nil { rw.WriteHeader(http.StatusNotFound) return } defer f.Close() rw.Header().Set("Content-Type", mime.TypeByExtension(filepath.Ext(path))) io.Copy(rw, f) } func StatusHandler(rw http.ResponseWriter, req *http.Request) { writeJSON(rw, map[string]interface{}{ "running": handler(req).fetchRunning, "stats": map[string]int64{}, }) } type NewFolder struct { Title string `json:"title"` } func FolderListHandler(rw http.ResponseWriter, req *http.Request) { if req.Method == "GET" { list := db(req).ListFolders() json.NewEncoder(rw).Encode(list) } else if req.Method == "POST" { var body NewFolder if err := json.NewDecoder(req.Body).Decode(&body); err != nil { log.Print(err) rw.WriteHeader(http.StatusBadRequest) return } if len(body.Title) == 0 { rw.WriteHeader(http.StatusBadRequest) writeJSON(rw, map[string]string{"error": "Folder title missing."}) return } folder := db(req).CreateFolder(body.Title) rw.WriteHeader(http.StatusCreated) json.NewEncoder(rw).Encode(folder) } else { rw.WriteHeader(http.StatusMethodNotAllowed) } } type UpdateFolder struct { Title *string `json:"title,omitempty"` } func FolderHandler(rw http.ResponseWriter, req *http.Request) { id, err := strconv.ParseInt(Vars(req)["id"], 10, 64) if err != nil { rw.WriteHeader(http.StatusBadRequest) return } if req.Method == "PUT" { var body UpdateFolder if err := json.NewDecoder(req.Body).Decode(&body); err != nil { log.Print(err) rw.WriteHeader(http.StatusBadRequest) return } if body.Title != nil { db(req).RenameFolder(id, *body.Title) } rw.WriteHeader(http.StatusOK) } else if req.Method == "DELETE" { db(req).DeleteFolder(id) rw.WriteHeader(http.StatusNoContent) } } type NewFeed struct { Url string `json:"url"` FolderID *int64 `json:"folder_id,omitempty"` } type UpdateFeed struct { Title *string `json:"title,omitempty"` FolderID *int64 `json:"folder_id,omitempty"` } func FeedListHandler(rw http.ResponseWriter, req *http.Request) { if req.Method == "GET" { list := db(req).ListFeeds() json.NewEncoder(rw).Encode(list) } else if req.Method == "POST" { var feed NewFeed if err := json.NewDecoder(req.Body).Decode(&feed); err != nil { log.Print(err) rw.WriteHeader(http.StatusBadRequest) return } feedUrl := feed.Url res, err := http.Get(feedUrl) if err != nil { log.Print(err) rw.WriteHeader(http.StatusBadRequest) return } else if res.StatusCode != 200 { rw.WriteHeader(http.StatusBadRequest) return } contentType := res.Header.Get("Content-Type") if strings.HasPrefix(contentType, "text/html") || contentType == "" { sources, err := FindFeeds(res) if err != nil { log.Print(err) rw.WriteHeader(http.StatusBadRequest) return } if len(sources) == 0 { writeJSON(rw, map[string]string{"status": "notfound"}) } else if len(sources) > 1 { writeJSON(rw, map[string]interface{}{ "status": "multiple", "choice": sources, }) } else if len(sources) == 1 { feedUrl = sources[0].Url err = createFeed(db(req), feedUrl, feed.FolderID) if err != nil { log.Print(err) rw.WriteHeader(http.StatusBadRequest) return } writeJSON(rw, map[string]string{"status": "success"}) } } else if strings.HasPrefix(contentType, "text/xml") || strings.HasPrefix(contentType, "application/xml") { err = createFeed(db(req), feedUrl, feed.FolderID) if err == nil { writeJSON(rw, map[string]string{"status": "success"}) } } else { rw.WriteHeader(http.StatusBadRequest) return } } } func convertItems(items []*gofeed.Item, feed storage.Feed) []storage.Item { result := make([]storage.Item, len(items)) for _, item := range items { imageURL := "" if item.Image != nil { imageURL = item.Image.URL } author := "" if item.Author != nil { author = item.Author.Name } result = append(result, storage.Item{ GUID: item.GUID, FeedId: feed.Id, Title: item.Title, Link: item.Link, Description: item.Description, Content: item.Content, Author: author, Date: item.PublishedParsed, DateUpdated: item.UpdatedParsed, Status: storage.UNREAD, Image: imageURL, }) } return result } func listItems(f storage.Feed) []storage.Item { fp := gofeed.NewParser() feed, err := fp.ParseURL(f.FeedLink) if err != nil { log.Print(err) return nil } return convertItems(feed.Items, f) } func createFeed(s *storage.Storage, url string, folderId *int64) error { fp := gofeed.NewParser() feed, err := fp.ParseURL(url) if err != nil { return err } feedLink := feed.FeedLink if len(feedLink) == 0 { feedLink = url } storedFeed := s.CreateFeed( feed.Title, feed.Description, feed.Link, feedLink, "", folderId, ) s.CreateItems(convertItems(feed.Items, *storedFeed)) return nil } func FeedHandler(rw http.ResponseWriter, req *http.Request) { id, err := strconv.ParseInt(Vars(req)["id"], 10, 64) if err != nil { rw.WriteHeader(http.StatusBadRequest) return } if req.Method == "PUT" { var body UpdateFeed if err := json.NewDecoder(req.Body).Decode(&body); err != nil { log.Print(err) rw.WriteHeader(http.StatusBadRequest) return } if body.Title != nil { db(req).RenameFeed(id, *body.Title) } if body.FolderID != nil { db(req).UpdateFeedFolder(id, *body.FolderID) } rw.WriteHeader(http.StatusOK) } else if req.Method == "DELETE" { db(req).DeleteFeed(id) rw.WriteHeader(http.StatusNoContent) } else { rw.WriteHeader(http.StatusMethodNotAllowed) } } type UpdateItem struct { Status *storage.ItemStatus `json:"status,omitempty"` } func ItemHandler(rw http.ResponseWriter, req *http.Request) { if req.Method == "PUT" { id, err := strconv.ParseInt(Vars(req)["id"], 10, 64) if err != nil { rw.WriteHeader(http.StatusBadRequest) return } var body UpdateItem if err := json.NewDecoder(req.Body).Decode(&body); err != nil { log.Print(err) rw.WriteHeader(http.StatusBadRequest) return } if body.Status != nil { db(req).UpdateItemStatus(id, *body.Status) } rw.WriteHeader(http.StatusOK) } else { rw.WriteHeader(http.StatusMethodNotAllowed) } } func ItemListHandler(rw http.ResponseWriter, req *http.Request) { if req.Method == "GET" { perPage := 20 curPage := 1 query := req.URL.Query() if page, err := strconv.ParseInt(query.Get("page"), 10, 64); err == nil { curPage = int(page) } filter := storage.ItemFilter{} if folderID, err := strconv.ParseInt(query.Get("folder_id"), 10, 64); err == nil { filter.FolderID = &folderID } if feedID, err := strconv.ParseInt(query.Get("feed_id"), 10, 64); err == nil { filter.FeedID = &feedID } if status := query.Get("status"); len(status) != 0 { statusValue := storage.StatusValues[status] filter.Status = &statusValue } items := db(req).ListItems(filter, (curPage-1)*perPage, perPage) count := db(req).CountItems(filter) rw.WriteHeader(http.StatusOK) writeJSON(rw, map[string]interface{}{ "page": map[string]int{ "cur": curPage, "num": int(math.Ceil(float64(count) / float64(perPage))), }, "list": items, }) } else if req.Method == "PUT" { query := req.URL.Query() filter := storage.ItemFilter{} if folderID, err := strconv.ParseInt(query.Get("folder_id"), 10, 64); err == nil { filter.FolderID = &folderID } if feedID, err := strconv.ParseInt(query.Get("feed_id"), 10, 64); err == nil { filter.FeedID = &feedID } db(req).MarkItemsRead(filter) rw.WriteHeader(http.StatusOK) } else { rw.WriteHeader(http.StatusMethodNotAllowed) } } func SettingsHandler(rw http.ResponseWriter, req *http.Request) { if req.Method == "GET" { rw.WriteHeader(http.StatusOK) writeJSON(rw, db(req).GetSettings()) } else if req.Method == "PUT" { settings := make(map[string]interface{}) if err := json.NewDecoder(req.Body).Decode(&settings); err != nil { rw.WriteHeader(http.StatusBadRequest) return } if db(req).UpdateSettings(settings) { rw.WriteHeader(http.StatusOK) } else { rw.WriteHeader(http.StatusBadRequest) } } }