entry.go 6.8 KB

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