entry.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. // Copyright 2017 Frédéric Guillot. All rights reserved.
  2. // Use of this source code is governed by the Apache 2.0
  3. // license that can be found in the LICENSE file.
  4. package api // import "miniflux.app/api"
  5. import (
  6. json_parser "encoding/json"
  7. "errors"
  8. "net/http"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "miniflux.app/config"
  13. "miniflux.app/http/request"
  14. "miniflux.app/http/response/json"
  15. "miniflux.app/model"
  16. "miniflux.app/proxy"
  17. "miniflux.app/reader/processor"
  18. "miniflux.app/storage"
  19. "miniflux.app/url"
  20. "miniflux.app/validator"
  21. )
  22. func (h *handler) getEntryFromBuilder(w http.ResponseWriter, r *http.Request, b *storage.EntryQueryBuilder) {
  23. entry, err := b.GetEntry()
  24. if err != nil {
  25. json.ServerError(w, r, err)
  26. return
  27. }
  28. if entry == nil {
  29. json.NotFound(w, r)
  30. return
  31. }
  32. entry.Content = proxy.AbsoluteImageProxyRewriter(h.router, r.Host, entry.Content)
  33. proxyImage := config.Opts.ProxyImages()
  34. for i := range entry.Enclosures {
  35. if strings.HasPrefix(entry.Enclosures[i].MimeType, "image/") && (proxyImage == "all" || proxyImage != "none" && !url.IsHTTPS(entry.Enclosures[i].URL)) {
  36. entry.Enclosures[i].URL = proxy.AbsoluteProxifyURL(h.router, r.Host, entry.Enclosures[i].URL)
  37. }
  38. }
  39. json.OK(w, r, entry)
  40. }
  41. func (h *handler) getFeedEntry(w http.ResponseWriter, r *http.Request) {
  42. feedID := request.RouteInt64Param(r, "feedID")
  43. entryID := request.RouteInt64Param(r, "entryID")
  44. builder := h.store.NewEntryQueryBuilder(request.UserID(r))
  45. builder.WithFeedID(feedID)
  46. builder.WithEntryID(entryID)
  47. h.getEntryFromBuilder(w, r, builder)
  48. }
  49. func (h *handler) getCategoryEntry(w http.ResponseWriter, r *http.Request) {
  50. categoryID := request.RouteInt64Param(r, "categoryID")
  51. entryID := request.RouteInt64Param(r, "entryID")
  52. builder := h.store.NewEntryQueryBuilder(request.UserID(r))
  53. builder.WithCategoryID(categoryID)
  54. builder.WithEntryID(entryID)
  55. h.getEntryFromBuilder(w, r, builder)
  56. }
  57. func (h *handler) getEntry(w http.ResponseWriter, r *http.Request) {
  58. entryID := request.RouteInt64Param(r, "entryID")
  59. builder := h.store.NewEntryQueryBuilder(request.UserID(r))
  60. builder.WithEntryID(entryID)
  61. h.getEntryFromBuilder(w, r, builder)
  62. }
  63. func (h *handler) getFeedEntries(w http.ResponseWriter, r *http.Request) {
  64. feedID := request.RouteInt64Param(r, "feedID")
  65. h.findEntries(w, r, feedID, 0)
  66. }
  67. func (h *handler) getCategoryEntries(w http.ResponseWriter, r *http.Request) {
  68. categoryID := request.RouteInt64Param(r, "categoryID")
  69. h.findEntries(w, r, 0, categoryID)
  70. }
  71. func (h *handler) getEntries(w http.ResponseWriter, r *http.Request) {
  72. h.findEntries(w, r, 0, 0)
  73. }
  74. func (h *handler) findEntries(w http.ResponseWriter, r *http.Request, feedID int64, categoryID int64) {
  75. statuses := request.QueryStringParamList(r, "status")
  76. for _, status := range statuses {
  77. if err := validator.ValidateEntryStatus(status); err != nil {
  78. json.BadRequest(w, r, err)
  79. return
  80. }
  81. }
  82. order := request.QueryStringParam(r, "order", model.DefaultSortingOrder)
  83. if err := validator.ValidateEntryOrder(order); err != nil {
  84. json.BadRequest(w, r, err)
  85. return
  86. }
  87. direction := request.QueryStringParam(r, "direction", model.DefaultSortingDirection)
  88. if err := validator.ValidateDirection(direction); err != nil {
  89. json.BadRequest(w, r, err)
  90. return
  91. }
  92. limit := request.QueryIntParam(r, "limit", 100)
  93. offset := request.QueryIntParam(r, "offset", 0)
  94. if err := validator.ValidateRange(offset, limit); err != nil {
  95. json.BadRequest(w, r, err)
  96. return
  97. }
  98. userID := request.UserID(r)
  99. categoryID = request.QueryInt64Param(r, "category_id", categoryID)
  100. if categoryID > 0 && !h.store.CategoryIDExists(userID, categoryID) {
  101. json.BadRequest(w, r, errors.New("Invalid category ID"))
  102. return
  103. }
  104. feedID = request.QueryInt64Param(r, "feed_id", feedID)
  105. if feedID > 0 && !h.store.FeedExists(userID, feedID) {
  106. json.BadRequest(w, r, errors.New("Invalid feed ID"))
  107. return
  108. }
  109. builder := h.store.NewEntryQueryBuilder(userID)
  110. builder.WithFeedID(feedID)
  111. builder.WithCategoryID(categoryID)
  112. builder.WithStatuses(statuses)
  113. builder.WithOrder(order)
  114. builder.WithDirection(direction)
  115. builder.WithOffset(offset)
  116. builder.WithLimit(limit)
  117. configureFilters(builder, r)
  118. entries, err := builder.GetEntries()
  119. if err != nil {
  120. json.ServerError(w, r, err)
  121. return
  122. }
  123. count, err := builder.CountEntries()
  124. if err != nil {
  125. json.ServerError(w, r, err)
  126. return
  127. }
  128. for i := range entries {
  129. entries[i].Content = proxy.AbsoluteImageProxyRewriter(h.router, r.Host, entries[i].Content)
  130. }
  131. json.OK(w, r, &entriesResponse{Total: count, Entries: entries})
  132. }
  133. func (h *handler) setEntryStatus(w http.ResponseWriter, r *http.Request) {
  134. var entriesStatusUpdateRequest model.EntriesStatusUpdateRequest
  135. if err := json_parser.NewDecoder(r.Body).Decode(&entriesStatusUpdateRequest); err != nil {
  136. json.BadRequest(w, r, err)
  137. return
  138. }
  139. if err := validator.ValidateEntriesStatusUpdateRequest(&entriesStatusUpdateRequest); err != nil {
  140. json.BadRequest(w, r, err)
  141. return
  142. }
  143. if err := h.store.SetEntriesStatus(request.UserID(r), entriesStatusUpdateRequest.EntryIDs, entriesStatusUpdateRequest.Status); err != nil {
  144. json.ServerError(w, r, err)
  145. return
  146. }
  147. json.NoContent(w, r)
  148. }
  149. func (h *handler) toggleBookmark(w http.ResponseWriter, r *http.Request) {
  150. entryID := request.RouteInt64Param(r, "entryID")
  151. if err := h.store.ToggleBookmark(request.UserID(r), entryID); err != nil {
  152. json.ServerError(w, r, err)
  153. return
  154. }
  155. json.NoContent(w, r)
  156. }
  157. func (h *handler) fetchContent(w http.ResponseWriter, r *http.Request) {
  158. loggedUserID := request.UserID(r)
  159. entryID := request.RouteInt64Param(r, "entryID")
  160. entryBuilder := h.store.NewEntryQueryBuilder(loggedUserID)
  161. entryBuilder.WithEntryID(entryID)
  162. entryBuilder.WithoutStatus(model.EntryStatusRemoved)
  163. entry, err := entryBuilder.GetEntry()
  164. if err != nil {
  165. json.ServerError(w, r, err)
  166. return
  167. }
  168. if entry == nil {
  169. json.NotFound(w, r)
  170. return
  171. }
  172. user, err := h.store.UserByID(entry.UserID)
  173. if err != nil {
  174. json.ServerError(w, r, err)
  175. return
  176. }
  177. if user == nil {
  178. json.NotFound(w, r)
  179. return
  180. }
  181. feedBuilder := storage.NewFeedQueryBuilder(h.store, loggedUserID)
  182. feedBuilder.WithFeedID(entry.FeedID)
  183. feed, err := feedBuilder.GetFeed()
  184. if err != nil {
  185. json.ServerError(w, r, err)
  186. return
  187. }
  188. if feed == nil {
  189. json.NotFound(w, r)
  190. return
  191. }
  192. if err := processor.ProcessEntryWebPage(feed, entry, user); err != nil {
  193. json.ServerError(w, r, err)
  194. return
  195. }
  196. json.OK(w, r, map[string]string{"content": entry.Content})
  197. }
  198. func configureFilters(builder *storage.EntryQueryBuilder, r *http.Request) {
  199. beforeEntryID := request.QueryInt64Param(r, "before_entry_id", 0)
  200. if beforeEntryID > 0 {
  201. builder.BeforeEntryID(beforeEntryID)
  202. }
  203. afterEntryID := request.QueryInt64Param(r, "after_entry_id", 0)
  204. if afterEntryID > 0 {
  205. builder.AfterEntryID(afterEntryID)
  206. }
  207. beforeTimestamp := request.QueryInt64Param(r, "before", 0)
  208. if beforeTimestamp > 0 {
  209. builder.BeforeDate(time.Unix(beforeTimestamp, 0))
  210. }
  211. afterTimestamp := request.QueryInt64Param(r, "after", 0)
  212. if afterTimestamp > 0 {
  213. builder.AfterDate(time.Unix(afterTimestamp, 0))
  214. }
  215. categoryID := request.QueryInt64Param(r, "category_id", 0)
  216. if categoryID > 0 {
  217. builder.WithCategoryID(categoryID)
  218. }
  219. if request.HasQueryParam(r, "starred") {
  220. starred, err := strconv.ParseBool(r.URL.Query().Get("starred"))
  221. if err == nil {
  222. builder.WithStarred(starred)
  223. }
  224. }
  225. searchQuery := request.QueryStringParam(r, "search", "")
  226. if searchQuery != "" {
  227. builder.WithSearchQuery(searchQuery)
  228. }
  229. }