source_monitor 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rubocop.yml +12 -0
  4. data/.ruby-version +1 -0
  5. data/AGENTS.md +132 -0
  6. data/CHANGELOG.md +66 -0
  7. data/CONTRIBUTING.md +31 -0
  8. data/Gemfile +30 -0
  9. data/Gemfile.lock +411 -0
  10. data/MIT-LICENSE +20 -0
  11. data/README.md +108 -0
  12. data/Rakefile +8 -0
  13. data/app/assets/builds/.keep +0 -0
  14. data/app/assets/config/source_monitor_manifest.js +4 -0
  15. data/app/assets/images/source_monitor/.keep +0 -0
  16. data/app/assets/javascripts/source_monitor/application.js +20 -0
  17. data/app/assets/javascripts/source_monitor/controllers/async_submit_controller.js +36 -0
  18. data/app/assets/javascripts/source_monitor/controllers/dropdown_controller.js +109 -0
  19. data/app/assets/javascripts/source_monitor/controllers/modal_controller.js +56 -0
  20. data/app/assets/javascripts/source_monitor/controllers/notification_controller.js +53 -0
  21. data/app/assets/javascripts/source_monitor/turbo_actions.js +13 -0
  22. data/app/assets/stylesheets/source_monitor/application.tailwind.css +13 -0
  23. data/app/assets/svgs/source_monitor/.keep +0 -0
  24. data/app/controllers/concerns/.keep +0 -0
  25. data/app/controllers/concerns/source_monitor/sanitizes_search_params.rb +81 -0
  26. data/app/controllers/source_monitor/application_controller.rb +62 -0
  27. data/app/controllers/source_monitor/dashboard_controller.rb +27 -0
  28. data/app/controllers/source_monitor/fetch_logs_controller.rb +9 -0
  29. data/app/controllers/source_monitor/health_controller.rb +10 -0
  30. data/app/controllers/source_monitor/items_controller.rb +116 -0
  31. data/app/controllers/source_monitor/logs_controller.rb +15 -0
  32. data/app/controllers/source_monitor/scrape_logs_controller.rb +9 -0
  33. data/app/controllers/source_monitor/source_bulk_scrapes_controller.rb +35 -0
  34. data/app/controllers/source_monitor/source_fetches_controller.rb +22 -0
  35. data/app/controllers/source_monitor/source_health_checks_controller.rb +34 -0
  36. data/app/controllers/source_monitor/source_health_resets_controller.rb +27 -0
  37. data/app/controllers/source_monitor/source_retries_controller.rb +22 -0
  38. data/app/controllers/source_monitor/source_turbo_responses.rb +115 -0
  39. data/app/controllers/source_monitor/sources_controller.rb +179 -0
  40. data/app/helpers/source_monitor/application_helper.rb +327 -0
  41. data/app/jobs/source_monitor/application_job.rb +13 -0
  42. data/app/jobs/source_monitor/fetch_feed_job.rb +117 -0
  43. data/app/jobs/source_monitor/item_cleanup_job.rb +48 -0
  44. data/app/jobs/source_monitor/log_cleanup_job.rb +47 -0
  45. data/app/jobs/source_monitor/schedule_fetches_job.rb +29 -0
  46. data/app/jobs/source_monitor/scrape_item_job.rb +47 -0
  47. data/app/jobs/source_monitor/source_health_check_job.rb +77 -0
  48. data/app/mailers/source_monitor/application_mailer.rb +17 -0
  49. data/app/models/concerns/.keep +0 -0
  50. data/app/models/concerns/source_monitor/loggable.rb +18 -0
  51. data/app/models/source_monitor/application_record.rb +5 -0
  52. data/app/models/source_monitor/fetch_log.rb +31 -0
  53. data/app/models/source_monitor/health_check_log.rb +28 -0
  54. data/app/models/source_monitor/item.rb +102 -0
  55. data/app/models/source_monitor/item_content.rb +11 -0
  56. data/app/models/source_monitor/log_entry.rb +56 -0
  57. data/app/models/source_monitor/scrape_log.rb +31 -0
  58. data/app/models/source_monitor/source.rb +115 -0
  59. data/app/views/layouts/source_monitor/application.html.erb +54 -0
  60. data/app/views/source_monitor/dashboard/_fetch_schedule.html.erb +90 -0
  61. data/app/views/source_monitor/dashboard/_job_metrics.html.erb +82 -0
  62. data/app/views/source_monitor/dashboard/_recent_activity.html.erb +39 -0
  63. data/app/views/source_monitor/dashboard/_stat_card.html.erb +6 -0
  64. data/app/views/source_monitor/dashboard/_stats.html.erb +9 -0
  65. data/app/views/source_monitor/dashboard/index.html.erb +48 -0
  66. data/app/views/source_monitor/fetch_logs/show.html.erb +90 -0
  67. data/app/views/source_monitor/items/_details.html.erb +234 -0
  68. data/app/views/source_monitor/items/_details_wrapper.html.erb +3 -0
  69. data/app/views/source_monitor/items/index.html.erb +147 -0
  70. data/app/views/source_monitor/items/show.html.erb +3 -0
  71. data/app/views/source_monitor/logs/index.html.erb +208 -0
  72. data/app/views/source_monitor/scrape_logs/show.html.erb +73 -0
  73. data/app/views/source_monitor/shared/_toast.html.erb +34 -0
  74. data/app/views/source_monitor/sources/_bulk_scrape_form.html.erb +64 -0
  75. data/app/views/source_monitor/sources/_bulk_scrape_modal.html.erb +53 -0
  76. data/app/views/source_monitor/sources/_details.html.erb +302 -0
  77. data/app/views/source_monitor/sources/_details_wrapper.html.erb +3 -0
  78. data/app/views/source_monitor/sources/_empty_state_row.html.erb +5 -0
  79. data/app/views/source_monitor/sources/_fetch_interval_heatmap.html.erb +46 -0
  80. data/app/views/source_monitor/sources/_form.html.erb +143 -0
  81. data/app/views/source_monitor/sources/_health_status_badge.html.erb +46 -0
  82. data/app/views/source_monitor/sources/_row.html.erb +102 -0
  83. data/app/views/source_monitor/sources/edit.html.erb +28 -0
  84. data/app/views/source_monitor/sources/index.html.erb +153 -0
  85. data/app/views/source_monitor/sources/new.html.erb +22 -0
  86. data/app/views/source_monitor/sources/show.html.erb +3 -0
  87. data/config/coverage_baseline.json +2010 -0
  88. data/config/initializers/feedjira.rb +19 -0
  89. data/config/routes.rb +18 -0
  90. data/config/tailwind.config.js +17 -0
  91. data/db/migrate/20241008120000_create_source_monitor_sources.rb +40 -0
  92. data/db/migrate/20241008121000_create_source_monitor_items.rb +44 -0
  93. data/db/migrate/20241008122000_create_source_monitor_fetch_logs.rb +32 -0
  94. data/db/migrate/20241008123000_create_source_monitor_scrape_logs.rb +25 -0
  95. data/db/migrate/20251008183000_change_fetch_interval_to_minutes.rb +23 -0
  96. data/db/migrate/20251009090000_create_source_monitor_item_contents.rb +38 -0
  97. data/db/migrate/20251009103000_add_feed_content_readability_to_sources.rb +5 -0
  98. data/db/migrate/20251010090000_add_adaptive_fetching_toggle_to_sources.rb +7 -0
  99. data/db/migrate/20251010123000_add_deleted_at_to_source_monitor_items.rb +8 -0
  100. data/db/migrate/20251010153000_add_type_to_source_monitor_sources.rb +8 -0
  101. data/db/migrate/20251010154500_add_fetch_status_to_source_monitor_sources.rb +9 -0
  102. data/db/migrate/20251010160000_create_solid_cable_messages.rb +16 -0
  103. data/db/migrate/20251011090000_add_fetch_retry_state_to_sources.rb +14 -0
  104. data/db/migrate/20251012090000_add_health_fields_to_sources.rb +17 -0
  105. data/db/migrate/20251012100000_optimize_source_monitor_database_performance.rb +13 -0
  106. data/db/migrate/20251014064947_add_not_null_constraints_to_items.rb +30 -0
  107. data/db/migrate/20251014171659_add_performance_indexes.rb +29 -0
  108. data/db/migrate/20251014172525_add_fetch_status_check_constraint.rb +18 -0
  109. data/db/migrate/20251015100000_create_source_monitor_log_entries.rb +89 -0
  110. data/db/migrate/20251022100000_create_source_monitor_health_check_logs.rb +22 -0
  111. data/db/migrate/20251108120116_refresh_fetch_status_constraint.rb +29 -0
  112. data/docs/configuration.md +170 -0
  113. data/docs/deployment.md +63 -0
  114. data/docs/gh-cli-workflow.md +44 -0
  115. data/docs/installation.md +144 -0
  116. data/docs/troubleshooting.md +76 -0
  117. data/eslint.config.mjs +27 -0
  118. data/lib/generators/source_monitor/install/install_generator.rb +59 -0
  119. data/lib/generators/source_monitor/install/templates/source_monitor.rb.tt +155 -0
  120. data/lib/source_monitor/analytics/source_activity_rates.rb +53 -0
  121. data/lib/source_monitor/analytics/source_fetch_interval_distribution.rb +57 -0
  122. data/lib/source_monitor/analytics/sources_index_metrics.rb +92 -0
  123. data/lib/source_monitor/assets/bundler.rb +49 -0
  124. data/lib/source_monitor/assets.rb +6 -0
  125. data/lib/source_monitor/configuration.rb +654 -0
  126. data/lib/source_monitor/dashboard/queries.rb +356 -0
  127. data/lib/source_monitor/dashboard/quick_action.rb +7 -0
  128. data/lib/source_monitor/dashboard/quick_actions_presenter.rb +26 -0
  129. data/lib/source_monitor/dashboard/recent_activity.rb +30 -0
  130. data/lib/source_monitor/dashboard/recent_activity_presenter.rb +77 -0
  131. data/lib/source_monitor/dashboard/turbo_broadcaster.rb +87 -0
  132. data/lib/source_monitor/dashboard/upcoming_fetch_schedule.rb +126 -0
  133. data/lib/source_monitor/engine.rb +107 -0
  134. data/lib/source_monitor/events.rb +110 -0
  135. data/lib/source_monitor/feedjira_extensions.rb +103 -0
  136. data/lib/source_monitor/fetching/advisory_lock.rb +54 -0
  137. data/lib/source_monitor/fetching/completion/event_publisher.rb +22 -0
  138. data/lib/source_monitor/fetching/completion/follow_up_handler.rb +37 -0
  139. data/lib/source_monitor/fetching/completion/retention_handler.rb +30 -0
  140. data/lib/source_monitor/fetching/feed_fetcher.rb +627 -0
  141. data/lib/source_monitor/fetching/fetch_error.rb +88 -0
  142. data/lib/source_monitor/fetching/fetch_runner.rb +142 -0
  143. data/lib/source_monitor/fetching/retry_policy.rb +85 -0
  144. data/lib/source_monitor/fetching/stalled_fetch_reconciler.rb +146 -0
  145. data/lib/source_monitor/health/source_health_check.rb +100 -0
  146. data/lib/source_monitor/health/source_health_monitor.rb +210 -0
  147. data/lib/source_monitor/health/source_health_reset.rb +68 -0
  148. data/lib/source_monitor/health.rb +46 -0
  149. data/lib/source_monitor/http.rb +85 -0
  150. data/lib/source_monitor/instrumentation.rb +52 -0
  151. data/lib/source_monitor/items/item_creator.rb +601 -0
  152. data/lib/source_monitor/items/retention_pruner.rb +146 -0
  153. data/lib/source_monitor/items/retention_strategies/destroy.rb +26 -0
  154. data/lib/source_monitor/items/retention_strategies/soft_delete.rb +50 -0
  155. data/lib/source_monitor/items/retention_strategies.rb +9 -0
  156. data/lib/source_monitor/jobs/cleanup_options.rb +85 -0
  157. data/lib/source_monitor/jobs/fetch_failure_subscriber.rb +129 -0
  158. data/lib/source_monitor/jobs/solid_queue_metrics.rb +199 -0
  159. data/lib/source_monitor/jobs/visibility.rb +133 -0
  160. data/lib/source_monitor/logs/entry_sync.rb +69 -0
  161. data/lib/source_monitor/logs/filter_set.rb +163 -0
  162. data/lib/source_monitor/logs/query.rb +81 -0
  163. data/lib/source_monitor/logs/table_presenter.rb +161 -0
  164. data/lib/source_monitor/metrics.rb +77 -0
  165. data/lib/source_monitor/model_extensions.rb +109 -0
  166. data/lib/source_monitor/models/sanitizable.rb +76 -0
  167. data/lib/source_monitor/models/url_normalizable.rb +84 -0
  168. data/lib/source_monitor/pagination/paginator.rb +90 -0
  169. data/lib/source_monitor/realtime/adapter.rb +97 -0
  170. data/lib/source_monitor/realtime/broadcaster.rb +237 -0
  171. data/lib/source_monitor/realtime.rb +17 -0
  172. data/lib/source_monitor/release/changelog.rb +59 -0
  173. data/lib/source_monitor/release/runner.rb +73 -0
  174. data/lib/source_monitor/scheduler.rb +82 -0
  175. data/lib/source_monitor/scrapers/base.rb +105 -0
  176. data/lib/source_monitor/scrapers/fetchers/http_fetcher.rb +97 -0
  177. data/lib/source_monitor/scrapers/parsers/readability_parser.rb +101 -0
  178. data/lib/source_monitor/scrapers/readability.rb +156 -0
  179. data/lib/source_monitor/scraping/bulk_result_presenter.rb +85 -0
  180. data/lib/source_monitor/scraping/bulk_source_scraper.rb +233 -0
  181. data/lib/source_monitor/scraping/enqueuer.rb +125 -0
  182. data/lib/source_monitor/scraping/item_scraper/adapter_resolver.rb +44 -0
  183. data/lib/source_monitor/scraping/item_scraper/persistence.rb +189 -0
  184. data/lib/source_monitor/scraping/item_scraper.rb +84 -0
  185. data/lib/source_monitor/scraping/scheduler.rb +43 -0
  186. data/lib/source_monitor/scraping/state.rb +79 -0
  187. data/lib/source_monitor/security/authentication.rb +85 -0
  188. data/lib/source_monitor/security/parameter_sanitizer.rb +42 -0
  189. data/lib/source_monitor/sources/turbo_stream_presenter.rb +54 -0
  190. data/lib/source_monitor/turbo_streams/stream_responder.rb +95 -0
  191. data/lib/source_monitor/version.rb +3 -0
  192. data/lib/source_monitor.rb +149 -0
  193. data/lib/tasks/recover_stalled_fetches.rake +16 -0
  194. data/lib/tasks/source_monitor_assets.rake +28 -0
  195. data/lib/tasks/source_monitor_tasks.rake +29 -0
  196. data/lib/tasks/test_smoke.rake +12 -0
  197. data/package-lock.json +3997 -0
  198. data/package.json +29 -0
  199. data/postcss.config.js +6 -0
  200. data/source_monitor.gemspec +46 -0
  201. data/stylelint.config.js +12 -0
  202. metadata +469 -0
data/Gemfile.lock ADDED
@@ -0,0 +1,411 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ source_monitor (0.1.1)
5
+ cssbundling-rails (~> 1.4)
6
+ faraday (~> 2.9)
7
+ faraday-follow_redirects (~> 0.4)
8
+ faraday-gzip (~> 3.0)
9
+ faraday-retry (~> 2.2)
10
+ feedjira (>= 3.2, < 5.0)
11
+ jsbundling-rails (~> 1.3)
12
+ nokolexbor (~> 0.5)
13
+ rails (>= 8.0.3, < 9.0)
14
+ ransack (~> 4.2)
15
+ ruby-readability (~> 0.7)
16
+ solid_cable (>= 3.0, < 4.0)
17
+ solid_queue (>= 0.3, < 3.0)
18
+ turbo-rails (~> 2.0)
19
+
20
+ GEM
21
+ remote: https://rubygems.org/
22
+ specs:
23
+ action_text-trix (2.1.15)
24
+ railties
25
+ actioncable (8.1.1)
26
+ actionpack (= 8.1.1)
27
+ activesupport (= 8.1.1)
28
+ nio4r (~> 2.0)
29
+ websocket-driver (>= 0.6.1)
30
+ zeitwerk (~> 2.6)
31
+ actionmailbox (8.1.1)
32
+ actionpack (= 8.1.1)
33
+ activejob (= 8.1.1)
34
+ activerecord (= 8.1.1)
35
+ activestorage (= 8.1.1)
36
+ activesupport (= 8.1.1)
37
+ mail (>= 2.8.0)
38
+ actionmailer (8.1.1)
39
+ actionpack (= 8.1.1)
40
+ actionview (= 8.1.1)
41
+ activejob (= 8.1.1)
42
+ activesupport (= 8.1.1)
43
+ mail (>= 2.8.0)
44
+ rails-dom-testing (~> 2.2)
45
+ actionpack (8.1.1)
46
+ actionview (= 8.1.1)
47
+ activesupport (= 8.1.1)
48
+ nokogiri (>= 1.8.5)
49
+ rack (>= 2.2.4)
50
+ rack-session (>= 1.0.1)
51
+ rack-test (>= 0.6.3)
52
+ rails-dom-testing (~> 2.2)
53
+ rails-html-sanitizer (~> 1.6)
54
+ useragent (~> 0.16)
55
+ actiontext (8.1.1)
56
+ action_text-trix (~> 2.1.15)
57
+ actionpack (= 8.1.1)
58
+ activerecord (= 8.1.1)
59
+ activestorage (= 8.1.1)
60
+ activesupport (= 8.1.1)
61
+ globalid (>= 0.6.0)
62
+ nokogiri (>= 1.8.5)
63
+ actionview (8.1.1)
64
+ activesupport (= 8.1.1)
65
+ builder (~> 3.1)
66
+ erubi (~> 1.11)
67
+ rails-dom-testing (~> 2.2)
68
+ rails-html-sanitizer (~> 1.6)
69
+ activejob (8.1.1)
70
+ activesupport (= 8.1.1)
71
+ globalid (>= 0.3.6)
72
+ activemodel (8.1.1)
73
+ activesupport (= 8.1.1)
74
+ activerecord (8.1.1)
75
+ activemodel (= 8.1.1)
76
+ activesupport (= 8.1.1)
77
+ timeout (>= 0.4.0)
78
+ activestorage (8.1.1)
79
+ actionpack (= 8.1.1)
80
+ activejob (= 8.1.1)
81
+ activerecord (= 8.1.1)
82
+ activesupport (= 8.1.1)
83
+ marcel (~> 1.0)
84
+ activesupport (8.1.1)
85
+ base64
86
+ bigdecimal
87
+ concurrent-ruby (~> 1.0, >= 1.3.1)
88
+ connection_pool (>= 2.2.5)
89
+ drb
90
+ i18n (>= 1.6, < 2)
91
+ json
92
+ logger (>= 1.4.2)
93
+ minitest (>= 5.1)
94
+ securerandom (>= 0.3)
95
+ tzinfo (~> 2.0, >= 2.0.5)
96
+ uri (>= 0.13.1)
97
+ addressable (2.8.7)
98
+ public_suffix (>= 2.0.2, < 7.0)
99
+ ast (2.4.3)
100
+ base64 (0.3.0)
101
+ bigdecimal (3.3.1)
102
+ brakeman (7.1.0)
103
+ racc
104
+ builder (3.3.0)
105
+ capybara (3.40.0)
106
+ addressable
107
+ matrix
108
+ mini_mime (>= 0.1.3)
109
+ nokogiri (~> 1.11)
110
+ rack (>= 1.6.0)
111
+ rack-test (>= 0.6.3)
112
+ regexp_parser (>= 1.5, < 3.0)
113
+ xpath (~> 3.2)
114
+ concurrent-ruby (1.3.5)
115
+ connection_pool (2.5.4)
116
+ crack (1.0.1)
117
+ bigdecimal
118
+ rexml
119
+ crass (1.0.6)
120
+ cssbundling-rails (1.4.3)
121
+ railties (>= 6.0.0)
122
+ date (3.5.0)
123
+ docile (1.4.1)
124
+ drb (2.2.3)
125
+ erb (5.1.3)
126
+ erubi (1.13.1)
127
+ et-orbi (1.4.0)
128
+ tzinfo
129
+ faraday (2.14.0)
130
+ faraday-net_http (>= 2.0, < 3.5)
131
+ json
132
+ logger
133
+ faraday-follow_redirects (0.4.0)
134
+ faraday (>= 1, < 3)
135
+ faraday-gzip (3.0.4)
136
+ faraday (>= 2.0, < 3)
137
+ zlib (~> 3.0)
138
+ faraday-net_http (3.4.1)
139
+ net-http (>= 0.5.0)
140
+ faraday-retry (2.3.2)
141
+ faraday (~> 2.0)
142
+ feedjira (4.0.1)
143
+ logger (>= 1.0, < 2)
144
+ loofah (>= 2.3.1, < 3)
145
+ sax-machine (>= 1.0, < 2)
146
+ fugit (1.12.1)
147
+ et-orbi (~> 1.4)
148
+ raabro (~> 1.4)
149
+ globalid (1.3.0)
150
+ activesupport (>= 6.1)
151
+ guess_html_encoding (0.0.11)
152
+ hashdiff (1.2.1)
153
+ i18n (1.14.7)
154
+ concurrent-ruby (~> 1.0)
155
+ io-console (0.8.1)
156
+ irb (1.15.3)
157
+ pp (>= 0.6.0)
158
+ rdoc (>= 4.0.0)
159
+ reline (>= 0.4.2)
160
+ jsbundling-rails (1.3.1)
161
+ railties (>= 6.0.0)
162
+ json (2.15.2)
163
+ language_server-protocol (3.17.0.5)
164
+ lint_roller (1.1.0)
165
+ logger (1.7.0)
166
+ loofah (2.24.1)
167
+ crass (~> 1.0.2)
168
+ nokogiri (>= 1.12.0)
169
+ mail (2.8.1)
170
+ mini_mime (>= 0.1.1)
171
+ net-imap
172
+ net-pop
173
+ net-smtp
174
+ marcel (1.1.0)
175
+ matrix (0.4.3)
176
+ mini_mime (1.1.5)
177
+ mini_portile2 (2.8.9)
178
+ minitest (5.26.0)
179
+ net-http (0.6.0)
180
+ uri
181
+ net-imap (0.5.12)
182
+ date
183
+ net-protocol
184
+ net-pop (0.1.2)
185
+ net-protocol
186
+ net-protocol (0.2.2)
187
+ timeout
188
+ net-smtp (0.5.1)
189
+ net-protocol
190
+ nio4r (2.7.4)
191
+ nokogiri (1.18.10)
192
+ mini_portile2 (~> 2.8.2)
193
+ racc (~> 1.4)
194
+ nokogiri (1.18.10-aarch64-linux-gnu)
195
+ racc (~> 1.4)
196
+ nokogiri (1.18.10-aarch64-linux-musl)
197
+ racc (~> 1.4)
198
+ nokogiri (1.18.10-arm-linux-gnu)
199
+ racc (~> 1.4)
200
+ nokogiri (1.18.10-arm-linux-musl)
201
+ racc (~> 1.4)
202
+ nokogiri (1.18.10-arm64-darwin)
203
+ racc (~> 1.4)
204
+ nokogiri (1.18.10-x86_64-darwin)
205
+ racc (~> 1.4)
206
+ nokogiri (1.18.10-x86_64-linux-gnu)
207
+ racc (~> 1.4)
208
+ nokogiri (1.18.10-x86_64-linux-musl)
209
+ racc (~> 1.4)
210
+ nokolexbor (0.6.2)
211
+ nokolexbor (0.6.2-arm64-darwin)
212
+ nokolexbor (0.6.2-x86_64-darwin)
213
+ nokolexbor (0.6.2-x86_64-linux)
214
+ parallel (1.27.0)
215
+ parser (3.3.9.0)
216
+ ast (~> 2.4.1)
217
+ racc
218
+ pg (1.6.2)
219
+ pg (1.6.2-aarch64-linux)
220
+ pg (1.6.2-aarch64-linux-musl)
221
+ pg (1.6.2-arm64-darwin)
222
+ pg (1.6.2-x86_64-darwin)
223
+ pg (1.6.2-x86_64-linux)
224
+ pg (1.6.2-x86_64-linux-musl)
225
+ pp (0.6.3)
226
+ prettyprint
227
+ prettyprint (0.2.0)
228
+ prism (1.5.1)
229
+ propshaft (1.3.1)
230
+ actionpack (>= 7.0.0)
231
+ activesupport (>= 7.0.0)
232
+ rack
233
+ psych (5.2.6)
234
+ date
235
+ stringio
236
+ public_suffix (6.0.2)
237
+ puma (7.1.0)
238
+ nio4r (~> 2.0)
239
+ raabro (1.4.0)
240
+ racc (1.8.1)
241
+ rack (3.2.4)
242
+ rack-session (2.1.1)
243
+ base64 (>= 0.1.0)
244
+ rack (>= 3.0.0)
245
+ rack-test (2.2.0)
246
+ rack (>= 1.3)
247
+ rackup (2.2.1)
248
+ rack (>= 3)
249
+ rails (8.1.1)
250
+ actioncable (= 8.1.1)
251
+ actionmailbox (= 8.1.1)
252
+ actionmailer (= 8.1.1)
253
+ actionpack (= 8.1.1)
254
+ actiontext (= 8.1.1)
255
+ actionview (= 8.1.1)
256
+ activejob (= 8.1.1)
257
+ activemodel (= 8.1.1)
258
+ activerecord (= 8.1.1)
259
+ activestorage (= 8.1.1)
260
+ activesupport (= 8.1.1)
261
+ bundler (>= 1.15.0)
262
+ railties (= 8.1.1)
263
+ rails-dom-testing (2.3.0)
264
+ activesupport (>= 5.0.0)
265
+ minitest
266
+ nokogiri (>= 1.6)
267
+ rails-html-sanitizer (1.6.2)
268
+ loofah (~> 2.21)
269
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
270
+ railties (8.1.1)
271
+ actionpack (= 8.1.1)
272
+ activesupport (= 8.1.1)
273
+ irb (~> 1.13)
274
+ rackup (>= 1.0.0)
275
+ rake (>= 12.2)
276
+ thor (~> 1.0, >= 1.2.2)
277
+ tsort (>= 0.2)
278
+ zeitwerk (~> 2.6)
279
+ rainbow (3.1.1)
280
+ rake (13.3.1)
281
+ ransack (4.4.1)
282
+ activerecord (>= 7.2)
283
+ activesupport (>= 7.2)
284
+ i18n
285
+ rdoc (6.15.1)
286
+ erb
287
+ psych (>= 4.0.0)
288
+ tsort
289
+ regexp_parser (2.11.3)
290
+ reline (0.6.2)
291
+ io-console (~> 0.5)
292
+ rexml (3.4.4)
293
+ rubocop (1.81.1)
294
+ json (~> 2.3)
295
+ language_server-protocol (~> 3.17.0.2)
296
+ lint_roller (~> 1.1.0)
297
+ parallel (~> 1.10)
298
+ parser (>= 3.3.0.2)
299
+ rainbow (>= 2.2.2, < 4.0)
300
+ regexp_parser (>= 2.9.3, < 3.0)
301
+ rubocop-ast (>= 1.47.1, < 2.0)
302
+ ruby-progressbar (~> 1.7)
303
+ unicode-display_width (>= 2.4.0, < 4.0)
304
+ rubocop-ast (1.47.1)
305
+ parser (>= 3.3.7.2)
306
+ prism (~> 1.4)
307
+ rubocop-performance (1.26.0)
308
+ lint_roller (~> 1.1)
309
+ rubocop (>= 1.75.0, < 2.0)
310
+ rubocop-ast (>= 1.44.0, < 2.0)
311
+ rubocop-rails (2.33.4)
312
+ activesupport (>= 4.2.0)
313
+ lint_roller (~> 1.1)
314
+ rack (>= 1.1)
315
+ rubocop (>= 1.75.0, < 2.0)
316
+ rubocop-ast (>= 1.44.0, < 2.0)
317
+ rubocop-rails-omakase (1.1.0)
318
+ rubocop (>= 1.72)
319
+ rubocop-performance (>= 1.24)
320
+ rubocop-rails (>= 2.30)
321
+ ruby-progressbar (1.13.0)
322
+ ruby-readability (0.7.2)
323
+ guess_html_encoding (>= 0.0.4)
324
+ nokogiri (>= 1.6.0)
325
+ rubyzip (3.2.2)
326
+ sax-machine (1.3.2)
327
+ securerandom (0.4.1)
328
+ selenium-webdriver (4.38.0)
329
+ base64 (~> 0.2)
330
+ logger (~> 1.4)
331
+ rexml (~> 3.2, >= 3.2.5)
332
+ rubyzip (>= 1.2.2, < 4.0)
333
+ websocket (~> 1.0)
334
+ simplecov (0.22.0)
335
+ docile (~> 1.1)
336
+ simplecov-html (~> 0.11)
337
+ simplecov_json_formatter (~> 0.1)
338
+ simplecov-html (0.13.2)
339
+ simplecov_json_formatter (0.1.4)
340
+ solid_cable (3.0.12)
341
+ actioncable (>= 7.2)
342
+ activejob (>= 7.2)
343
+ activerecord (>= 7.2)
344
+ railties (>= 7.2)
345
+ solid_queue (1.2.4)
346
+ activejob (>= 7.1)
347
+ activerecord (>= 7.1)
348
+ concurrent-ruby (>= 1.3.1)
349
+ fugit (~> 1.11)
350
+ railties (>= 7.1)
351
+ thor (>= 1.3.1)
352
+ stackprof (0.2.27)
353
+ stringio (3.1.7)
354
+ test-prof (1.4.4)
355
+ thor (1.4.0)
356
+ timeout (0.4.4)
357
+ tsort (0.2.0)
358
+ turbo-rails (2.0.20)
359
+ actionpack (>= 7.1.0)
360
+ railties (>= 7.1.0)
361
+ tzinfo (2.0.6)
362
+ concurrent-ruby (~> 1.0)
363
+ unicode-display_width (3.2.0)
364
+ unicode-emoji (~> 4.1)
365
+ unicode-emoji (4.1.0)
366
+ uri (1.1.0)
367
+ useragent (0.16.11)
368
+ vcr (6.3.1)
369
+ base64
370
+ webmock (3.26.1)
371
+ addressable (>= 2.8.0)
372
+ crack (>= 0.3.2)
373
+ hashdiff (>= 0.4.0, < 2.0.0)
374
+ websocket (1.2.11)
375
+ websocket-driver (0.8.0)
376
+ base64
377
+ websocket-extensions (>= 0.1.0)
378
+ websocket-extensions (0.1.5)
379
+ xpath (3.2.0)
380
+ nokogiri (~> 1.8)
381
+ zeitwerk (2.7.3)
382
+ zlib (3.2.1)
383
+
384
+ PLATFORMS
385
+ aarch64-linux-gnu
386
+ aarch64-linux-musl
387
+ arm-linux-gnu
388
+ arm-linux-musl
389
+ arm64-darwin
390
+ ruby
391
+ x86_64-darwin
392
+ x86_64-linux-gnu
393
+ x86_64-linux-musl
394
+
395
+ DEPENDENCIES
396
+ brakeman
397
+ capybara
398
+ pg
399
+ propshaft
400
+ puma
401
+ rubocop-rails-omakase
402
+ selenium-webdriver
403
+ simplecov
404
+ source_monitor!
405
+ stackprof
406
+ test-prof
407
+ vcr
408
+ webmock
409
+
410
+ BUNDLED WITH
411
+ 2.7.2
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright dchuk
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # SourceMonitor
2
+
3
+ SourceMonitor is a production-ready Rails 8 mountable engine for ingesting, normalising, scraping, and monitoring RSS/Atom/JSON feeds. It ships with a Tailwind-powered admin UI, Solid Queue job orchestration, Solid Cable realtime broadcasting, and an extensible configuration layer so host applications can offer full-stack feed operations without rebuilding infrastructure.
4
+
5
+ ## Highlights
6
+ - Full-featured source and item administration backed by Turbo Streams and Tailwind UI components
7
+ - Adaptive fetch pipeline (Feedjira + Faraday) with conditional GETs, retention pruning, and scrape orchestration
8
+ - Realtime dashboard metrics, batching/caching query layer, and Mission Control integration hooks
9
+ - Extensible scraper adapters (Readability included) with per-source settings and structured result metadata
10
+ - Declarative configuration DSL covering queues, HTTP, retention, events, model extensions, authentication, and realtime transports
11
+ - First-class observability through ActiveSupport notifications and `SourceMonitor::Metrics` counters/gauges
12
+
13
+ ## Requirements
14
+ - Ruby 3.4.4 (we recommend [rbenv](https://github.com/rbenv/rbenv) for local development: `rbenv install 3.4.4 && rbenv local 3.4.4`, but use whatever Ruby version manager suits your environment—asdf, chruby, rvm, or container-based workflows all work fine)
15
+ - Rails ≥ 8.0.2.1 in the host application
16
+ - PostgreSQL 13+ (engine migrations use JSONB, SKIP LOCKED, advisory locks, and Solid Cable tables)
17
+ - Node.js 18+ (npm or Yarn) for asset linting and the Tailwind/esbuild bundling pipeline
18
+ - Solid Queue workers (Rails 8 default) and Solid Cable (default realtime adapter)
19
+ - Optional: Mission Control Jobs for dashboard linking, Redis if you opt into the Redis realtime adapter
20
+
21
+ ## Quick Start (Host Application)
22
+
23
+ > **Note on commands:** The examples below use bare `bundle` and `bin/rails` commands. If you use rbenv, prefix them with `rbenv exec` (e.g., `rbenv exec bundle install`). For Docker/container environments, run commands directly or via your container runtime. Adjust to match your Ruby version management approach.
24
+
25
+ ### Initial Install
26
+ 1. Add `gem "source_monitor", github: "dchuk/source_monitor"` to your Gemfile and run `bundle install`.
27
+ 2. Install the engine: `bin/rails generate source_monitor:install --mount-path=/source_monitor` (updates routes, drops the initializer, prints doc links).
28
+ 3. Copy migrations: `bin/rails railties:install:migrations FROM=source_monitor`.
29
+ 4. Apply migrations: `bin/rails db:migrate` (creates sources/items/logs tables, Solid Cable messages, and Solid Queue schema when required).
30
+ 5. Install frontend tooling if you plan to extend engine assets: `npm install`.
31
+ 6. Start background workers: `bin/rails solid_queue:start` (or your preferred process manager).
32
+ 7. Boot your app and visit the mount path you chose (default `/source_monitor`) to explore the dashboard and confirm Solid Queue metrics render.
33
+
34
+ Detailed instructions, optional flags, and verification steps live in [docs/installation.md](docs/installation.md), with troubleshooting advice in [docs/troubleshooting.md](docs/troubleshooting.md).
35
+
36
+ ### Upgrading SourceMonitor
37
+ 1. Bump the gem version in your host `Gemfile` and run `bundle install` (or `bundle update source_monitor` when targeting a specific release).
38
+ 2. Re-run `bin/rails railties:install:migrations FROM=source_monitor` and then `bin/rails db:migrate` to pick up schema changes.
39
+ 3. Compare your `config/initializers/source_monitor.rb` against the newly generated template for configuration diffs (new queue knobs, HTTP options, etc.).
40
+ 4. Review release notes for optional integrations—when enabling Mission Control, ensure `mission_control-jobs` stays mounted and linked via `config.mission_control_dashboard_path`.
41
+ 5. Smoke test Solid Queue workers, Action Cable, and admin UI flows after the upgrade.
42
+
43
+ ## Example Applications
44
+ - `examples/basic_host/template.rb` – Minimal host that seeds a Rails blog source and redirects `/` to the dashboard.
45
+ - `examples/advanced_host/template.rb` – Production-style integration with Mission Control, Redis realtime, Solid Queue tuning, and metrics endpoint.
46
+ - `examples/custom_adapter/template.rb` – Registers the sample Markdown scraper adapter and seeds a Markdown-based source.
47
+ - `examples/docker` – Dockerfile, Compose stack, and entrypoint script that run any generated example alongside Postgres and Redis.
48
+
49
+ See [examples/README.md](examples/README.md) for usage instructions.
50
+
51
+ ## Architecture at a Glance
52
+ - **Source Lifecycle** – `SourceMonitor::Fetching::FetchRunner` coordinates advisory locking, fetch execution, retention pruning, and scrape enqueues. Source models store health metrics, failure states, and adaptive scheduling parameters.
53
+ - **Item Processing** – `SourceMonitor::Items::RetentionPruner`, `SourceMonitor::Scraping::Enqueuer`, and `SourceMonitor::Scraping::ItemScraper` keep content fresh, ensure deduplicated storage, and capture scrape metadata/logs.
54
+ - **Scraping Pipeline** – Adapters inherit from `SourceMonitor::Scrapers::Base`, merging default + source + invocation settings and returning structured results. The bundled Readability adapter composes `SourceMonitor::Scrapers::Fetchers::HttpFetcher` and `SourceMonitor::Scrapers::Parsers::ReadabilityParser`.
55
+ - **Realtime Dashboard** – `SourceMonitor::Dashboard::Queries` batches SQL, caches per-request responses, emits instrumentation (`source_monitor.dashboard.*`), and coordinates Turbo broadcasts via Solid Cable.
56
+ - **Observability** – `SourceMonitor::Metrics` tracks counters/gauges for fetches, scheduler runs, and dashboard activity. ActiveSupport notifications (`source_monitor.fetch.*`, `source_monitor.scheduler.run`, etc.) let you instrument external systems without monkey patches.
57
+ - **Extensibility** – `SourceMonitor.configure` exposes namespaces for queue tuning, HTTP defaults, scraper registry, retention, event callbacks, model extensions, authentication hooks, realtime transports, health thresholds, and job metrics.
58
+
59
+ ## Admin Experience
60
+ - Dashboard cards summarising source counts, recent activity, queue visibility, and upcoming fetch schedules
61
+ - Source CRUD with scraping toggles, adaptive fetch controls, manual fetch triggers, and detailed fetch log timelines
62
+ - Item explorer showing feed vs scraped content, scrape status badges, and manual scrape actions via Turbo
63
+ - Fetch/scrape log viewers with HTTP status, duration, backtrace, and Solid Queue job references
64
+
65
+ ## Background Jobs & Scheduling
66
+ - Solid Queue becomes the Active Job adapter when the host app still uses the inline `:async` adapter; queue names default to `source_monitor_fetch` and `source_monitor_scrape` and honour `ActiveJob.queue_name_prefix`.
67
+ - `config/recurring.yml` schedules minute-level fetches and scrapes. Run `bin/jobs --recurring_schedule_file=config/recurring.yml` (or set `SOLID_QUEUE_RECURRING_SCHEDULE_FILE`) to load recurring tasks. Disable with `SOLID_QUEUE_SKIP_RECURRING=true`.
68
+ - Retry/backoff behaviour is driven by `SourceMonitor.configure.fetching`. Fetch completion events and item processors allow you to chain downstream workflows (indexing, notifications, etc.).
69
+
70
+ ## Configuration & API Surface
71
+ The generated initializer documents every setting. Key areas:
72
+
73
+ - Queue namespace/concurrency helpers (`SourceMonitor.queue_name(:fetch)`)
74
+ - HTTP, retry, and proxy settings (Faraday-backed)
75
+ - Scraper registry (`config.scrapers.register(:my_adapter, "MyApp::Scrapers::Custom")`)
76
+ - Retention defaults (`config.retention.items_retention_days`, `config.retention.strategy`)
77
+ - Lifecycle hooks (`config.events.after_item_created`, `config.events.register_item_processor`)
78
+ - Model extensions (table prefixes, included concerns, custom validations)
79
+ - Realtime adapter selection (`config.realtime.adapter = :solid_cable | :redis | :async`)
80
+ - Authentication helpers (`config.authentication.authenticate_with`, `authorize_with`, etc.)
81
+ - Mission Control toggles (`config.mission_control_enabled`, `config.mission_control_dashboard_path`)
82
+ - Health thresholds driving automatic pause/resume
83
+
84
+ See [docs/configuration.md](docs/configuration.md) for exhaustive coverage and examples.
85
+
86
+ ## Deployment Considerations
87
+ - Copy engine migrations before every deploy and run `bin/rails db:migrate`.
88
+ - Precompile assets so SourceMonitor's bundled CSS/JS outputs are available at runtime.
89
+ - Run dedicated Solid Queue worker processes; consider a separate scheduler process for recurring jobs.
90
+ - Configure Action Cable (Solid Cable by default) and expose `/cable` through your load balancer.
91
+ - Monitor gauges/counters emitted by `SourceMonitor::Metrics` and subscribe to notifications for alerting.
92
+
93
+ More production guidance, including process topology and scaling tips, is available in [docs/deployment.md](docs/deployment.md).
94
+
95
+ ## Troubleshooting & Support
96
+ Common installation and runtime issues (missing migrations, realtime not streaming, scraping failures, queue visibility gaps) are documented in [docs/troubleshooting.md](docs/troubleshooting.md). When you report bugs, include your `SourceMonitor::VERSION`, Rails version, configuration snippet, and relevant fetch/scrape logs so we can reproduce quickly.
97
+
98
+ ## Development & Testing (Engine Repository)
99
+ - Install dependencies with `bundle install` and `npm install` (prefix with `rbenv exec` if using rbenv).
100
+ - Use `test/dummy/bin/dev` to boot the dummy app with npm CSS/JS watchers, Solid Queue worker, and Rails server.
101
+ - Run tests via `bin/test-coverage` (SimpleCov-enforced), `bundle exec rake app:test:smoke` for the fast subset, or `bin/rails test` for targeted suites.
102
+ - Quality checks: `bin/rubocop`, `bin/brakeman --no-pager`, `bin/lint-assets`.
103
+ - Record HTTP fixtures with VCR under `test/vcr_cassettes/` and keep coverage ≥ 90% for new code.
104
+
105
+ Contributions follow the clean architecture and TDD guidelines in `.ai/project_overview.md`. Review `.ai/tasks.md` to align with the active roadmap slice before opening a pull request.
106
+
107
+ ## License
108
+ SourceMonitor is released under the [MIT License](MIT-LICENSE).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
File without changes
@@ -0,0 +1,4 @@
1
+ //= link_tree ../builds/source_monitor .css
2
+ //= link_tree ../builds/source_monitor .js
3
+ //= link_tree ../images/source_monitor
4
+ //= link_tree ../svgs/source_monitor
File without changes
@@ -0,0 +1,20 @@
1
+ import { Application } from "@hotwired/stimulus";
2
+ import AsyncSubmitController from "./controllers/async_submit_controller";
3
+ import NotificationController from "./controllers/notification_controller";
4
+ import DropdownController from "./controllers/dropdown_controller";
5
+ import ModalController from "./controllers/modal_controller";
6
+ import "./turbo_actions";
7
+
8
+ const existingApplication = window.SourceMonitorStimulus;
9
+ const application = existingApplication || Application.start();
10
+
11
+ if (!existingApplication) {
12
+ window.SourceMonitorStimulus = application;
13
+ }
14
+
15
+ application.register("notification", NotificationController);
16
+ application.register("async-submit", AsyncSubmitController);
17
+ application.register("dropdown", DropdownController);
18
+ application.register("modal", ModalController);
19
+
20
+ export default application;
@@ -0,0 +1,36 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["button"];
5
+ static values = { loadingText: String };
6
+
7
+ connect() {
8
+ if (this.hasButtonTarget) {
9
+ this.defaultText = this.buttonTarget.textContent;
10
+ }
11
+ }
12
+
13
+ start() {
14
+ if (!this.hasButtonTarget) return;
15
+
16
+ const button = this.buttonTarget;
17
+ button.disabled = true;
18
+ button.dataset.originalText = this.defaultText || button.textContent;
19
+ if (this.hasLoadingTextValue && this.loadingTextValue) {
20
+ button.textContent = this.loadingTextValue;
21
+ }
22
+ button.classList.add("opacity-75", "pointer-events-none");
23
+ }
24
+
25
+ finish() {
26
+ if (!this.hasButtonTarget) return;
27
+
28
+ const button = this.buttonTarget;
29
+ button.disabled = false;
30
+ button.classList.remove("opacity-75", "pointer-events-none");
31
+ const original = button.dataset.originalText;
32
+ if (original) {
33
+ button.textContent = original;
34
+ }
35
+ }
36
+ }