feed.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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 storage // import "miniflux.app/storage"
  5. import (
  6. "database/sql"
  7. "errors"
  8. "fmt"
  9. "runtime"
  10. "miniflux.app/config"
  11. "miniflux.app/logger"
  12. "miniflux.app/model"
  13. )
  14. // FeedExists checks if the given feed exists.
  15. func (s *Storage) FeedExists(userID, feedID int64) bool {
  16. var result bool
  17. query := `SELECT true FROM feeds WHERE user_id=$1 AND id=$2`
  18. s.db.QueryRow(query, userID, feedID).Scan(&result)
  19. return result
  20. }
  21. // FeedURLExists checks if feed URL already exists.
  22. func (s *Storage) FeedURLExists(userID int64, feedURL string) bool {
  23. var result bool
  24. query := `SELECT true FROM feeds WHERE user_id=$1 AND feed_url=$2`
  25. s.db.QueryRow(query, userID, feedURL).Scan(&result)
  26. return result
  27. }
  28. // AnotherFeedURLExists checks if the user a duplicated feed.
  29. func (s *Storage) AnotherFeedURLExists(userID, feedID int64, feedURL string) bool {
  30. var result bool
  31. query := `SELECT true FROM feeds WHERE id <> $1 AND user_id=$2 AND feed_url=$3`
  32. s.db.QueryRow(query, feedID, userID, feedURL).Scan(&result)
  33. return result
  34. }
  35. // CountAllFeeds returns the number of feeds in the database.
  36. func (s *Storage) CountAllFeeds() map[string]int64 {
  37. rows, err := s.db.Query(`SELECT disabled, count(*) FROM feeds GROUP BY disabled`)
  38. if err != nil {
  39. return nil
  40. }
  41. defer rows.Close()
  42. results := make(map[string]int64)
  43. results["enabled"] = 0
  44. results["disabled"] = 0
  45. for rows.Next() {
  46. var disabled bool
  47. var count int64
  48. if err := rows.Scan(&disabled, &count); err != nil {
  49. continue
  50. }
  51. if disabled {
  52. results["disabled"] = count
  53. } else {
  54. results["enabled"] = count
  55. }
  56. }
  57. results["total"] = results["disabled"] + results["enabled"]
  58. return results
  59. }
  60. // CountFeeds returns the number of feeds that belongs to the given user.
  61. func (s *Storage) CountFeeds(userID int64) int {
  62. var result int
  63. err := s.db.QueryRow(`SELECT count(*) FROM feeds WHERE user_id=$1`, userID).Scan(&result)
  64. if err != nil {
  65. return 0
  66. }
  67. return result
  68. }
  69. // CountUserFeedsWithErrors returns the number of feeds with parsing errors that belong to the given user.
  70. func (s *Storage) CountUserFeedsWithErrors(userID int64) int {
  71. pollingParsingErrorLimit := config.Opts.PollingParsingErrorLimit()
  72. if pollingParsingErrorLimit <= 0 {
  73. pollingParsingErrorLimit = 1
  74. }
  75. query := `SELECT count(*) FROM feeds WHERE user_id=$1 AND parsing_error_count >= $2`
  76. var result int
  77. err := s.db.QueryRow(query, userID, pollingParsingErrorLimit).Scan(&result)
  78. if err != nil {
  79. return 0
  80. }
  81. return result
  82. }
  83. // CountAllFeedsWithErrors returns the number of feeds with parsing errors.
  84. func (s *Storage) CountAllFeedsWithErrors() int {
  85. pollingParsingErrorLimit := config.Opts.PollingParsingErrorLimit()
  86. if pollingParsingErrorLimit <= 0 {
  87. pollingParsingErrorLimit = 1
  88. }
  89. query := `SELECT count(*) FROM feeds WHERE parsing_error_count >= $1`
  90. var result int
  91. err := s.db.QueryRow(query, pollingParsingErrorLimit).Scan(&result)
  92. if err != nil {
  93. return 0
  94. }
  95. return result
  96. }
  97. // Feeds returns all feeds that belongs to the given user.
  98. func (s *Storage) Feeds(userID int64) (model.Feeds, error) {
  99. builder := NewFeedQueryBuilder(s, userID)
  100. builder.WithOrder(model.DefaultFeedSorting)
  101. builder.WithDirection(model.DefaultFeedSortingDirection)
  102. return builder.GetFeeds()
  103. }
  104. // FeedsWithCounters returns all feeds of the given user with counters of read and unread entries.
  105. func (s *Storage) FeedsWithCounters(userID int64) (model.Feeds, error) {
  106. builder := NewFeedQueryBuilder(s, userID)
  107. builder.WithCounters()
  108. builder.WithOrder(model.DefaultFeedSorting)
  109. builder.WithDirection(model.DefaultFeedSortingDirection)
  110. return builder.GetFeeds()
  111. }
  112. // FeedsByCategoryWithCounters returns all feeds of the given user/category with counters of read and unread entries.
  113. func (s *Storage) FeedsByCategoryWithCounters(userID, categoryID int64) (model.Feeds, error) {
  114. builder := NewFeedQueryBuilder(s, userID)
  115. builder.WithCategoryID(categoryID)
  116. builder.WithCounters()
  117. builder.WithOrder(model.DefaultFeedSorting)
  118. builder.WithDirection(model.DefaultFeedSortingDirection)
  119. return builder.GetFeeds()
  120. }
  121. // WeeklyFeedEntryCount returns the weekly entry count for a feed.
  122. func (s *Storage) WeeklyFeedEntryCount(userID, feedID int64) (int, error) {
  123. query := `
  124. SELECT
  125. count(*)
  126. FROM
  127. entries
  128. WHERE
  129. entries.user_id=$1 AND
  130. entries.feed_id=$2 AND
  131. entries.published_at BETWEEN (now() - interval '1 week') AND now();
  132. `
  133. var weeklyCount int
  134. err := s.db.QueryRow(query, userID, feedID).Scan(&weeklyCount)
  135. switch {
  136. case errors.Is(err, sql.ErrNoRows):
  137. return 0, nil
  138. case err != nil:
  139. return 0, fmt.Errorf(`store: unable to fetch weekly count for feed #%d: %v`, feedID, err)
  140. }
  141. return weeklyCount, nil
  142. }
  143. // FeedByID returns a feed by the ID.
  144. func (s *Storage) FeedByID(userID, feedID int64) (*model.Feed, error) {
  145. builder := NewFeedQueryBuilder(s, userID)
  146. builder.WithFeedID(feedID)
  147. feed, err := builder.GetFeed()
  148. switch {
  149. case errors.Is(err, sql.ErrNoRows):
  150. return nil, nil
  151. case err != nil:
  152. return nil, fmt.Errorf(`store: unable to fetch feed #%d: %v`, feedID, err)
  153. }
  154. return feed, nil
  155. }
  156. // CreateFeed creates a new feed.
  157. func (s *Storage) CreateFeed(feed *model.Feed) error {
  158. sql := `
  159. INSERT INTO feeds (
  160. feed_url,
  161. site_url,
  162. title,
  163. category_id,
  164. user_id,
  165. etag_header,
  166. last_modified_header,
  167. crawler,
  168. user_agent,
  169. cookie,
  170. username,
  171. password,
  172. disabled,
  173. scraper_rules,
  174. rewrite_rules,
  175. blocklist_rules,
  176. keeplist_rules,
  177. ignore_http_cache,
  178. allow_self_signed_certificates,
  179. fetch_via_proxy
  180. )
  181. VALUES
  182. ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)
  183. RETURNING
  184. id
  185. `
  186. err := s.db.QueryRow(
  187. sql,
  188. feed.FeedURL,
  189. feed.SiteURL,
  190. feed.Title,
  191. feed.Category.ID,
  192. feed.UserID,
  193. feed.EtagHeader,
  194. feed.LastModifiedHeader,
  195. feed.Crawler,
  196. feed.UserAgent,
  197. feed.Cookie,
  198. feed.Username,
  199. feed.Password,
  200. feed.Disabled,
  201. feed.ScraperRules,
  202. feed.RewriteRules,
  203. feed.BlocklistRules,
  204. feed.KeeplistRules,
  205. feed.IgnoreHTTPCache,
  206. feed.AllowSelfSignedCertificates,
  207. feed.FetchViaProxy,
  208. ).Scan(&feed.ID)
  209. if err != nil {
  210. return fmt.Errorf(`store: unable to create feed %q: %v`, feed.FeedURL, err)
  211. }
  212. for i := 0; i < len(feed.Entries); i++ {
  213. feed.Entries[i].FeedID = feed.ID
  214. feed.Entries[i].UserID = feed.UserID
  215. tx, err := s.db.Begin()
  216. if err != nil {
  217. return fmt.Errorf(`store: unable to start transaction: %v`, err)
  218. }
  219. if !s.entryExists(tx, feed.Entries[i]) {
  220. if err := s.createEntry(tx, feed.Entries[i]); err != nil {
  221. tx.Rollback()
  222. return err
  223. }
  224. }
  225. if err := tx.Commit(); err != nil {
  226. return fmt.Errorf(`store: unable to commit transaction: %v`, err)
  227. }
  228. }
  229. return nil
  230. }
  231. // UpdateFeed updates an existing feed.
  232. func (s *Storage) UpdateFeed(feed *model.Feed) (err error) {
  233. query := `
  234. UPDATE
  235. feeds
  236. SET
  237. feed_url=$1,
  238. site_url=$2,
  239. title=$3,
  240. category_id=$4,
  241. etag_header=$5,
  242. last_modified_header=$6,
  243. checked_at=$7,
  244. parsing_error_msg=$8,
  245. parsing_error_count=$9,
  246. scraper_rules=$10,
  247. rewrite_rules=$11,
  248. blocklist_rules=$12,
  249. keeplist_rules=$13,
  250. crawler=$14,
  251. user_agent=$15,
  252. cookie=$16,
  253. username=$17,
  254. password=$18,
  255. disabled=$19,
  256. next_check_at=$20,
  257. ignore_http_cache=$21,
  258. allow_self_signed_certificates=$22,
  259. fetch_via_proxy=$23
  260. WHERE
  261. id=$24 AND user_id=$25
  262. `
  263. _, err = s.db.Exec(query,
  264. feed.FeedURL,
  265. feed.SiteURL,
  266. feed.Title,
  267. feed.Category.ID,
  268. feed.EtagHeader,
  269. feed.LastModifiedHeader,
  270. feed.CheckedAt,
  271. feed.ParsingErrorMsg,
  272. feed.ParsingErrorCount,
  273. feed.ScraperRules,
  274. feed.RewriteRules,
  275. feed.BlocklistRules,
  276. feed.KeeplistRules,
  277. feed.Crawler,
  278. feed.UserAgent,
  279. feed.Cookie,
  280. feed.Username,
  281. feed.Password,
  282. feed.Disabled,
  283. feed.NextCheckAt,
  284. feed.IgnoreHTTPCache,
  285. feed.AllowSelfSignedCertificates,
  286. feed.FetchViaProxy,
  287. feed.ID,
  288. feed.UserID,
  289. )
  290. if err != nil {
  291. return fmt.Errorf(`store: unable to update feed #%d (%s): %v`, feed.ID, feed.FeedURL, err)
  292. }
  293. return nil
  294. }
  295. // UpdateFeedError updates feed errors.
  296. func (s *Storage) UpdateFeedError(feed *model.Feed) (err error) {
  297. query := `
  298. UPDATE
  299. feeds
  300. SET
  301. parsing_error_msg=$1,
  302. parsing_error_count=$2,
  303. checked_at=$3,
  304. next_check_at=$4
  305. WHERE
  306. id=$5 AND user_id=$6
  307. `
  308. _, err = s.db.Exec(query,
  309. feed.ParsingErrorMsg,
  310. feed.ParsingErrorCount,
  311. feed.CheckedAt,
  312. feed.NextCheckAt,
  313. feed.ID,
  314. feed.UserID,
  315. )
  316. if err != nil {
  317. return fmt.Errorf(`store: unable to update feed error #%d (%s): %v`, feed.ID, feed.FeedURL, err)
  318. }
  319. return nil
  320. }
  321. // RemoveFeed removes a feed and all entries.
  322. // This operation can takes time if the feed has lot of entries.
  323. func (s *Storage) RemoveFeed(userID, feedID int64) error {
  324. rows, err := s.db.Query(`SELECT id FROM entries WHERE user_id=$1 AND feed_id=$2`, userID, feedID)
  325. if err != nil {
  326. return fmt.Errorf(`store: unable to get user feed entries: %v`, err)
  327. }
  328. defer rows.Close()
  329. for rows.Next() {
  330. var entryID int64
  331. if err := rows.Scan(&entryID); err != nil {
  332. return fmt.Errorf(`store: unable to read user feed entry ID: %v`, err)
  333. }
  334. logger.Debug(`[FEED DELETION] Deleting entry #%d of feed #%d for user #%d (%d GoRoutines)`, entryID, feedID, userID, runtime.NumGoroutine())
  335. if _, err := s.db.Exec(`DELETE FROM entries WHERE id=$1 AND user_id=$2`, entryID, userID); err != nil {
  336. return fmt.Errorf(`store: unable to delete user feed entries #%d: %v`, entryID, err)
  337. }
  338. }
  339. if _, err := s.db.Exec(`DELETE FROM feeds WHERE id=$1 AND user_id=$2`, feedID, userID); err != nil {
  340. return fmt.Errorf(`store: unable to delete feed #%d: %v`, feedID, err)
  341. }
  342. return nil
  343. }
  344. // ResetFeedErrors removes all feed errors.
  345. func (s *Storage) ResetFeedErrors() error {
  346. _, err := s.db.Exec(`UPDATE feeds SET parsing_error_count=0, parsing_error_msg=''`)
  347. return err
  348. }