source_monitor 0.13.0 → 0.14.0
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.
- checksums.yaml +4 -4
- data/.claude/skills/sm-configuration-setting/reference/settings-catalog.md +1 -0
- data/.claude/skills/sm-configure/SKILL.md +8 -1
- data/.claude/skills/sm-configure/reference/configuration-reference.md +11 -0
- data/.claude/skills/sm-event-handler/SKILL.md +1 -1
- data/.claude/skills/sm-event-handler/reference/events-api.md +1 -1
- data/.claude/skills/sm-host-setup/SKILL.md +13 -3
- data/.claude/skills/sm-host-setup/reference/initializer-template.md +11 -0
- data/.claude/skills/sm-host-setup/reference/setup-checklist.md +9 -1
- data/.claude/skills/sm-upgrade/reference/version-history.md +12 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile.lock +1 -1
- data/README.md +3 -3
- data/VERSION +1 -1
- data/app/assets/builds/source_monitor/application.css +4 -0
- data/app/controllers/source_monitor/application_controller.rb +73 -14
- data/app/controllers/source_monitor/bulk_scrape_enablements_controller.rb +1 -1
- data/app/controllers/source_monitor/import_sessions/bulk_configuration.rb +3 -1
- data/app/controllers/source_monitor/import_sessions_controller.rb +118 -72
- data/app/controllers/source_monitor/sources_controller.rb +4 -18
- data/app/models/source_monitor/source.rb +1 -1
- data/app/views/layouts/source_monitor/application.html.erb +6 -0
- data/docs/configuration.md +18 -1
- data/docs/deployment.md +1 -1
- data/docs/goals/engine-hardening/.goalbuddy-board/app.js +543 -0
- data/docs/goals/engine-hardening/.goalbuddy-board/goalbuddy-mark.png +0 -0
- data/docs/goals/engine-hardening/.goalbuddy-board/index.html +111 -0
- data/docs/goals/engine-hardening/.goalbuddy-board/styles.css +991 -0
- data/docs/goals/engine-hardening/goal.md +97 -0
- data/docs/goals/engine-hardening/notes/T001-spec-validation.md +37 -0
- data/docs/goals/engine-hardening/state.yaml +324 -0
- data/docs/setup.md +3 -3
- data/docs/upgrade.md +41 -0
- data/lib/generators/source_monitor/install/templates/source_monitor.rb.tt +10 -0
- data/lib/source_monitor/analytics/scrape_recommendations.rb +21 -2
- data/lib/source_monitor/configuration/authentication_settings.rb +5 -1
- data/lib/source_monitor/fetching/feed_fetcher/failure_outcome.rb +85 -0
- data/lib/source_monitor/fetching/feed_fetcher/success_outcome.rb +85 -0
- data/lib/source_monitor/fetching/feed_fetcher.rb +27 -88
- data/lib/source_monitor/fetching/fetch_runner.rb +12 -5
- data/lib/source_monitor/import_sessions/wizard.rb +612 -0
- data/lib/source_monitor/items/batch_item_creator.rb +7 -6
- data/lib/source_monitor/items/item_creator.rb +7 -14
- data/lib/source_monitor/items/normalized_entry.rb +61 -0
- data/lib/source_monitor/security/authentication.rb +10 -0
- data/lib/source_monitor/version.rb +1 -1
- data/lib/source_monitor.rb +2 -0
- data/source_monitor.gemspec +7 -2
- metadata +12 -68
- data/.claude/agent-memory/vbw-vbw-debugger/MEMORY.md +0 -15
- data/.claude/agent-memory/vbw-vbw-dev/MEMORY.md +0 -34
- data/.claude/agent-memory/vbw-vbw-lead/MEMORY.md +0 -49
- data/.claude/agents/rails-concern.md +0 -464
- data/.claude/agents/rails-controller.md +0 -424
- data/.claude/agents/rails-hotwire.md +0 -446
- data/.claude/agents/rails-implement.md +0 -374
- data/.claude/agents/rails-job.md +0 -334
- data/.claude/agents/rails-lint.md +0 -294
- data/.claude/agents/rails-mailer.md +0 -371
- data/.claude/agents/rails-migration.md +0 -449
- data/.claude/agents/rails-model.md +0 -420
- data/.claude/agents/rails-policy.md +0 -443
- data/.claude/agents/rails-presenter.md +0 -427
- data/.claude/agents/rails-query.md +0 -412
- data/.claude/agents/rails-review.md +0 -490
- data/.claude/agents/rails-service.md +0 -458
- data/.claude/agents/rails-state-records.md +0 -465
- data/.claude/agents/rails-tdd.md +0 -314
- data/.claude/agents/rails-test.md +0 -441
- data/.claude/agents/rails-view-component.md +0 -418
- data/.claude/commands/rails-audit.md +0 -77
- data/.claude/commands/release.md +0 -366
- data/.claude/hooks/block-secrets.sh +0 -52
- data/.claude/settings.json +0 -85
- data/.claude/skills/action-cable-patterns/SKILL.md +0 -296
- data/.claude/skills/action-mailer-patterns/SKILL.md +0 -295
- data/.claude/skills/active-storage-setup/SKILL.md +0 -311
- data/.claude/skills/api-versioning/SKILL.md +0 -294
- data/.claude/skills/authentication-flow/SKILL.md +0 -335
- data/.claude/skills/authentication-flow/reference/current.md +0 -248
- data/.claude/skills/authentication-flow/reference/passwordless.md +0 -253
- data/.claude/skills/authentication-flow/reference/sessions.md +0 -201
- data/.claude/skills/authorization-pundit/SKILL.md +0 -462
- data/.claude/skills/caching-strategies/SKILL.md +0 -350
- data/.claude/skills/database-migrations/SKILL.md +0 -354
- data/.claude/skills/form-object-patterns/SKILL.md +0 -399
- data/.claude/skills/hotwire-patterns/SKILL.md +0 -247
- data/.claude/skills/hotwire-patterns/reference/stimulus.md +0 -307
- data/.claude/skills/hotwire-patterns/reference/tailwind-integration.md +0 -112
- data/.claude/skills/hotwire-patterns/reference/turbo-frames.md +0 -158
- data/.claude/skills/hotwire-patterns/reference/turbo-streams.md +0 -218
- data/.claude/skills/i18n-patterns/SKILL.md +0 -320
- data/.claude/skills/install/SKILL.md +0 -367
- data/.claude/skills/performance-optimization/SKILL.md +0 -311
- data/.claude/skills/rails-architecture/SKILL.md +0 -259
- data/.claude/skills/rails-architecture/reference/error-handling.md +0 -333
- data/.claude/skills/rails-architecture/reference/event-tracking.md +0 -142
- data/.claude/skills/rails-architecture/reference/layer-interactions.md +0 -417
- data/.claude/skills/rails-architecture/reference/multi-tenancy.md +0 -152
- data/.claude/skills/rails-architecture/reference/query-patterns.md +0 -342
- data/.claude/skills/rails-architecture/reference/service-patterns.md +0 -286
- data/.claude/skills/rails-architecture/reference/state-records.md +0 -250
- data/.claude/skills/rails-architecture/reference/testing-strategy.md +0 -326
- data/.claude/skills/rails-concern/SKILL.md +0 -399
- data/.claude/skills/rails-controller/SKILL.md +0 -336
- data/.claude/skills/rails-model-generator/SKILL.md +0 -321
- data/.claude/skills/rails-model-generator/reference/validations.md +0 -298
- data/.claude/skills/rails-presenter/SKILL.md +0 -274
- data/.claude/skills/rails-query-object/SKILL.md +0 -289
- data/.claude/skills/rails-service-object/SKILL.md +0 -349
- data/.claude/skills/solid-queue-setup/SKILL.md +0 -307
- data/.claude/skills/tdd-cycle/SKILL.md +0 -359
- data/.claude/skills/viewcomponent-patterns/SKILL.md +0 -333
- data/app/controllers/source_monitor/import_sessions/entry_annotation.rb +0 -187
- data/app/controllers/source_monitor/import_sessions/health_check_management.rb +0 -112
- data/app/controllers/source_monitor/import_sessions/opml_parser.rb +0 -130
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35e62d7d750d8d9fe1cff82806de1846d27fe505fad819551e48474f864b1499
|
|
4
|
+
data.tar.gz: 2f6889306b930aa78caec52cc92c7c6919ba379955a6d0484cc8bf44f52dcf6f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3ca0f0c0a9d6e1c2557c501a2a51bc9dee52e486b78cb78f1384232f5af704c209026e514f1dea2ea94c6962d502856add17c57b4afc79c462ed8fa39da8d236
|
|
7
|
+
data.tar.gz: ed49ec4ce672c5850b2bc3f3a6d73e31f1d08d6c8f7b1a779258db69ac9cdb48d1aa6e8478784a0f89bf161ec81bad31b79485455373a702240a1580ad31f6dc
|
|
@@ -157,6 +157,7 @@ Has `reset!` method. The `adapter=` setter validates against `VALID_ADAPTERS`.
|
|
|
157
157
|
| `authorize_handler` | Handler/nil | `nil` | Authorization handler |
|
|
158
158
|
| `current_user_method` | Symbol/nil | `nil` | Method name for current user |
|
|
159
159
|
| `user_signed_in_method` | Symbol/nil | `nil` | Method name for signed-in check |
|
|
160
|
+
| `open_access` | Boolean | `false` | Opt out of the fail-closed access guard (demo/non-production only); ignored when a handler is configured |
|
|
160
161
|
|
|
161
162
|
Has `reset!` method.
|
|
162
163
|
|
|
@@ -66,10 +66,17 @@ config.http.retry_max = 3
|
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
### Authentication (Devise)
|
|
69
|
+
SourceMonitor is **fail-closed by default**: with no `authenticate_with`/`authorize_with`
|
|
70
|
+
handler configured, every engine route returns `403 Forbidden`. Configure a handler
|
|
71
|
+
to protect the dashboard (the handler decides access and bypasses the fail-closed guard):
|
|
69
72
|
```ruby
|
|
70
73
|
config.authentication.authenticate_with :authenticate_user!
|
|
71
74
|
config.authentication.authorize_with ->(c) { c.current_user&.admin? }
|
|
72
75
|
```
|
|
76
|
+
For local demos/sandboxes only, opt out of the fail-closed guard (non-production):
|
|
77
|
+
```ruby
|
|
78
|
+
config.authentication.open_access = true # default: false -- demo/non-production only
|
|
79
|
+
```
|
|
73
80
|
|
|
74
81
|
### Image Downloads (Active Storage)
|
|
75
82
|
```ruby
|
|
@@ -167,7 +174,7 @@ end
|
|
|
167
174
|
- [ ] Initializer exists at `config/initializers/source_monitor.rb`
|
|
168
175
|
- [ ] Queue names match `config/queue.yml` (or `config/solid_queue.yml`) entries
|
|
169
176
|
- [x] Dispatcher config includes `recurring_schedule: config/recurring.yml` (handled by install generator)
|
|
170
|
-
- [ ] Authentication
|
|
177
|
+
- [ ] Authentication decision made: configure `authenticate_with`/`authorize_with` (fail-closed default) OR set `config.authentication.open_access = true` for demos only
|
|
171
178
|
- [ ] HTTP timeouts appropriate for target feeds
|
|
172
179
|
- [ ] Retention policy set for production
|
|
173
180
|
- [ ] Workers restarted after configuration changes
|
|
@@ -283,10 +283,17 @@ config.realtime.solid_cable.connects_to = { database: { writing: :cable } }
|
|
|
283
283
|
|
|
284
284
|
Class: `SourceMonitor::Configuration::AuthenticationSettings`
|
|
285
285
|
|
|
286
|
+
**Fail-closed by default.** When no `authenticate_with`/`authorize_with` handler is
|
|
287
|
+
configured and `open_access` is `false`, the engine denies every route with
|
|
288
|
+
`403 Forbidden` (see `SourceMonitor::Security::Authentication.access_denied_by_default?`).
|
|
289
|
+
Configuring either handler makes the handler authoritative and bypasses the
|
|
290
|
+
fail-closed guard.
|
|
291
|
+
|
|
286
292
|
| Setting | Type | Default | Description |
|
|
287
293
|
|---|---|---|---|
|
|
288
294
|
| `current_user_method` | Symbol/nil | `nil` | Controller method to get current user |
|
|
289
295
|
| `user_signed_in_method` | Symbol/nil | `nil` | Controller method to check sign-in status |
|
|
296
|
+
| `open_access` | Boolean | `false` | Opt out of the fail-closed guard so engine routes are public. **Demo/non-production only.** Ignored when a handler is configured. |
|
|
290
297
|
|
|
291
298
|
### Methods
|
|
292
299
|
|
|
@@ -312,6 +319,10 @@ config.authentication.authorize_with ->(controller) {
|
|
|
312
319
|
config.authentication.authorize_with do
|
|
313
320
|
redirect_to root_path unless current_user&.admin?
|
|
314
321
|
end
|
|
322
|
+
|
|
323
|
+
# Open access (demo/non-production only) -- disables the fail-closed guard.
|
|
324
|
+
# Leave commented/false in production. A configured handler always wins.
|
|
325
|
+
config.authentication.open_access = true # default: false
|
|
315
326
|
```
|
|
316
327
|
|
|
317
328
|
---
|
|
@@ -98,7 +98,7 @@ Fires after a feed fetch finishes (success or failure).
|
|
|
98
98
|
| Field | Type | Description |
|
|
99
99
|
|---|---|---|
|
|
100
100
|
| `source` | `SourceMonitor::Source` | The fetched source |
|
|
101
|
-
| `result` |
|
|
101
|
+
| `result` | `SourceMonitor::Fetching::FeedFetcher::Result` | The fetch result |
|
|
102
102
|
| `status` | String | Result status |
|
|
103
103
|
| `occurred_at` | Time | When the event fired |
|
|
104
104
|
|
|
@@ -112,7 +112,7 @@ Fired by `Events.after_fetch_completed` after a feed fetch finishes.
|
|
|
112
112
|
```ruby
|
|
113
113
|
FetchCompletedEvent = Struct.new(
|
|
114
114
|
:source, # SourceMonitor::Source - the fetched source
|
|
115
|
-
:result, #
|
|
115
|
+
:result, # SourceMonitor::Fetching::FeedFetcher::Result - fetch result
|
|
116
116
|
:status, # String - result status
|
|
117
117
|
:occurred_at, # Time - when the event fired
|
|
118
118
|
keyword_init: true
|
|
@@ -119,7 +119,7 @@ After installation, review and customize the initializer. Key areas:
|
|
|
119
119
|
| Section | Purpose |
|
|
120
120
|
|---|---|
|
|
121
121
|
| Queue settings | Queue names, concurrency, namespace |
|
|
122
|
-
| Authentication | `authenticate_with
|
|
122
|
+
| Authentication | **Fail-closed by default** -- configure `authenticate_with`/`authorize_with` hooks, or set `open_access = true` for demos only |
|
|
123
123
|
| HTTP client | Timeouts, proxy, retries |
|
|
124
124
|
| Fetching | Adaptive scheduling intervals and factors |
|
|
125
125
|
| Health | Auto-pause/resume thresholds |
|
|
@@ -152,7 +152,17 @@ SOURCE_MONITOR_SETUP_TELEMETRY=true bin/source_monitor verify
|
|
|
152
152
|
# Logs to log/source_monitor_setup.log
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
##
|
|
155
|
+
## Authentication (Fail-Closed by Default)
|
|
156
|
+
|
|
157
|
+
SourceMonitor is **fail-closed by default**: with no `authenticate_with`/`authorize_with`
|
|
158
|
+
handler configured, every engine route (including create/update/delete/enqueue actions)
|
|
159
|
+
returns `403 Forbidden`. You must make one of two choices during setup:
|
|
160
|
+
|
|
161
|
+
1. **Configure a handler** (recommended) -- the handler decides access and bypasses the
|
|
162
|
+
fail-closed guard.
|
|
163
|
+
2. **Set `config.authentication.open_access = true`** -- opts out of the guard so routes
|
|
164
|
+
are public. **Demo/non-production only.** A configured handler always takes precedence
|
|
165
|
+
over this flag.
|
|
156
166
|
|
|
157
167
|
When Devise is detected, the guided installer offers to wire authentication hooks:
|
|
158
168
|
|
|
@@ -238,6 +248,6 @@ end
|
|
|
238
248
|
- [x] `Procfile.dev` includes `jobs:` entry for Solid Queue (handled by generator)
|
|
239
249
|
- [x] Dispatcher config includes `recurring_schedule: config/recurring.yml` (handled by generator)
|
|
240
250
|
- [ ] Solid Queue workers started
|
|
241
|
-
- [ ] Authentication
|
|
251
|
+
- [ ] Authentication decision made (engine is fail-closed by default): handler configured via `authenticate_with`/`authorize_with`, OR `config.authentication.open_access = true` for demos only
|
|
242
252
|
- [ ] `bin/source_monitor verify` passes
|
|
243
253
|
- [ ] Dashboard accessible at mount path
|
|
@@ -57,7 +57,12 @@ SourceMonitor.configure do |config|
|
|
|
57
57
|
# ===========================================================================
|
|
58
58
|
# Authentication
|
|
59
59
|
# ===========================================================================
|
|
60
|
+
# SECURITY: SourceMonitor is FAIL-CLOSED by default. If you do not configure
|
|
61
|
+
# an authentication or authorization handler below, every engine route
|
|
62
|
+
# (including create/update/delete/enqueue actions) returns 403 Forbidden.
|
|
60
63
|
# Handlers: Symbol (invoked on controller) or callable (receives controller).
|
|
64
|
+
# As soon as a handler is configured it decides access and the fail-closed
|
|
65
|
+
# guard no longer applies.
|
|
61
66
|
|
|
62
67
|
# Authenticate before accessing any SourceMonitor page.
|
|
63
68
|
# config.authentication.authenticate_with :authenticate_user!
|
|
@@ -71,6 +76,12 @@ SourceMonitor.configure do |config|
|
|
|
71
76
|
# config.authentication.current_user_method = :current_user
|
|
72
77
|
# config.authentication.user_signed_in_method = :user_signed_in?
|
|
73
78
|
|
|
79
|
+
# Explicit opt-in for open/unauthenticated access. Leave commented out in
|
|
80
|
+
# production. Only enable for local demos or sandboxes where engine routes
|
|
81
|
+
# are deliberately public.
|
|
82
|
+
# WARNING: non-production / demo only -- this disables the fail-closed guard.
|
|
83
|
+
# config.authentication.open_access = true
|
|
84
|
+
|
|
74
85
|
# ===========================================================================
|
|
75
86
|
# HTTP Client
|
|
76
87
|
# ===========================================================================
|
|
@@ -57,6 +57,10 @@ bin/rails db:migrate
|
|
|
57
57
|
|
|
58
58
|
## Phase 5: Configure Authentication
|
|
59
59
|
|
|
60
|
+
**Fail-closed by default.** With no handler configured, every engine route returns
|
|
61
|
+
`403 Forbidden`. Make one of two choices: configure a handler (recommended) OR opt into
|
|
62
|
+
open access for demos only.
|
|
63
|
+
|
|
60
64
|
Edit `config/initializers/source_monitor.rb`:
|
|
61
65
|
|
|
62
66
|
```ruby
|
|
@@ -68,10 +72,14 @@ SourceMonitor.configure do |config|
|
|
|
68
72
|
}
|
|
69
73
|
config.authentication.current_user_method = :current_user
|
|
70
74
|
config.authentication.user_signed_in_method = :user_signed_in?
|
|
75
|
+
|
|
76
|
+
# OR, for local demos/sandboxes only (non-production) -- opt out of the
|
|
77
|
+
# fail-closed guard so routes are public. A configured handler always wins.
|
|
78
|
+
# config.authentication.open_access = true
|
|
71
79
|
end
|
|
72
80
|
```
|
|
73
81
|
|
|
74
|
-
- [ ] Authentication
|
|
82
|
+
- [ ] Authentication decision made: handler configured (fail-closed default) OR `open_access = true` set for demos only
|
|
75
83
|
- [ ] Authorization hook configured (if needed)
|
|
76
84
|
|
|
77
85
|
## Phase 6: Configure Workers
|
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Version-specific migration notes for each major/minor version transition. Agents should reference this file when guiding users through multi-version upgrades.
|
|
4
4
|
|
|
5
|
+
## 0.13.1 to 0.14.0
|
|
6
|
+
|
|
7
|
+
**Key changes:**
|
|
8
|
+
- **BREAKING — Fail-closed by default** (#129): engine routes return `403 Forbidden` unless `config.authentication.authenticate_with` or `config.authentication.authorize_with` is configured, OR `config.authentication.open_access = true` is explicitly set (non-production only).
|
|
9
|
+
- **Request flashes response-local** (#130): flash toasts no longer broadcast to the global `source_monitor_notifications` ActionCable stream; rendered inline on HTML loads and appended on turbo_stream responses instead. Background operational toasts stay global but context-free.
|
|
10
|
+
- **Gem package excludes `.claude` internals** (#131): only `.claude/skills/sm-*` shipped; agents, hooks, `settings.json`, commands, and non-`sm-*` skills excluded.
|
|
11
|
+
|
|
12
|
+
**Action items:**
|
|
13
|
+
1. `bundle update source_monitor`
|
|
14
|
+
2. **Action required (auth):** in `config/initializers/source_monitor.rb`, ensure at least one of `authenticate_with`, `authorize_with`, or `open_access = true` (demo/sandbox only). Without one, all engine routes return `403`.
|
|
15
|
+
3. No migrations or other breaking API changes; flash/packaging changes apply transparently.
|
|
16
|
+
|
|
5
17
|
## 0.12.4 to 0.13.0
|
|
6
18
|
|
|
7
19
|
**Key changes:**
|
data/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,25 @@ All notable changes to this project are documented below. The format follows [Ke
|
|
|
15
15
|
|
|
16
16
|
- No unreleased changes yet.
|
|
17
17
|
|
|
18
|
+
## [0.14.0] - 2026-05-28
|
|
19
|
+
|
|
20
|
+
### Security (BREAKING)
|
|
21
|
+
- **Fail-closed engine access by default** (#129). When the host app has configured no authentication/authorization handler, every mounted SourceMonitor route now returns `403 Forbidden` instead of being publicly accessible. Previously `authenticate!`/`authorize!` were silent no-ops when unconfigured, so create/update/delete/enqueue routes were public by omission.
|
|
22
|
+
- **Migration:** Configure a handler — `config.authentication.authenticate_with` and/or `config.authentication.authorize_with` — to gate the engine with your host auth stack. Configured handlers are honored exactly as before.
|
|
23
|
+
- **Demo opt-in:** To intentionally keep routes public (local demos/sandboxes only), set `config.authentication.open_access = true` (default `false`). Do not enable this in production.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- **Request flashes no longer leak across users** (#130). Request flash toasts were broadcast to a global ActionCable stream (`source_monitor_notifications`) that every connected browser tab subscribed to, so one user's flash could appear on every user's screen. Flashes are now delivered response-local — rendered inline on full-page HTML loads and appended to the turbo_stream response otherwise — reaching only the requesting tab. Background operational toasts (fetch/scrape completion) remain global but carry no per-user request context.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- **Gem package no longer ships `.claude` internals** (#131). The packaged gem now excludes `.claude` agents, hooks, agent-memory, `settings.json`, commands, and non-`sm-*` skills; only the intended `.claude/skills/sm-*` SourceMonitor skills are shipped. Packaging is also now CWD-independent so the skill files are included deterministically.
|
|
30
|
+
|
|
31
|
+
## [0.13.1] - 2026-05-28
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- Preserve the public `after_fetch_completed` payload as `FeedFetcher::Result`; the PR #118 fetch outcome objects remain an internal refactor detail.
|
|
35
|
+
- Keep inactive sources out of sources-index scrape recommendation badges, matching the shared recommendation query used by dashboard and bulk enablement.
|
|
36
|
+
|
|
18
37
|
## [0.13.0] - 2026-03-24
|
|
19
38
|
|
|
20
39
|
### Changed
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -9,8 +9,8 @@ SourceMonitor is a production-ready Rails 8 mountable engine for ingesting, norm
|
|
|
9
9
|
In your host Rails app:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
bundle add source_monitor --version "~> 0.
|
|
13
|
-
# or add `gem "source_monitor", "~> 0.
|
|
12
|
+
bundle add source_monitor --version "~> 0.14.0"
|
|
13
|
+
# or add `gem "source_monitor", "~> 0.14.0"` manually, then run:
|
|
14
14
|
bundle install
|
|
15
15
|
```
|
|
16
16
|
|
|
@@ -46,7 +46,7 @@ This exposes `bin/source_monitor` (via Bundler binstubs) so you can run the guid
|
|
|
46
46
|
Before running any SourceMonitor commands inside your host app, add the gem and install dependencies:
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
bundle add source_monitor --version "~> 0.
|
|
49
|
+
bundle add source_monitor --version "~> 0.14.0"
|
|
50
50
|
# or edit your Gemfile, then run
|
|
51
51
|
bundle install
|
|
52
52
|
```
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.14.0
|
|
@@ -1927,6 +1927,10 @@ video {
|
|
|
1927
1927
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
1928
1928
|
}
|
|
1929
1929
|
|
|
1930
|
+
.fm-admin .outline {
|
|
1931
|
+
outline-style: solid;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1930
1934
|
.fm-admin .ring-1 {
|
|
1931
1935
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
|
1932
1936
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
|
@@ -4,11 +4,13 @@ module SourceMonitor
|
|
|
4
4
|
class ApplicationController < ActionController::Base
|
|
5
5
|
protect_from_forgery with: :exception, prepend: true
|
|
6
6
|
|
|
7
|
+
before_action :enforce_source_monitor_access_default
|
|
7
8
|
before_action :authenticate_source_monitor_user
|
|
8
9
|
before_action :authorize_source_monitor_access
|
|
9
10
|
|
|
10
|
-
helper_method :source_monitor_current_user, :source_monitor_user_signed_in
|
|
11
|
-
|
|
11
|
+
helper_method :source_monitor_current_user, :source_monitor_user_signed_in?,
|
|
12
|
+
:source_monitor_flash_toasts
|
|
13
|
+
after_action :append_flash_toasts_to_turbo_stream
|
|
12
14
|
|
|
13
15
|
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
|
|
14
16
|
|
|
@@ -40,6 +42,30 @@ module SourceMonitor
|
|
|
40
42
|
TOAST_DURATION_DEFAULT = 5000
|
|
41
43
|
TOAST_DURATION_ERROR = 6000
|
|
42
44
|
|
|
45
|
+
# Fail-closed guard: when the host app has configured no authentication or
|
|
46
|
+
# authorization handler and has not explicitly opted into open access, deny
|
|
47
|
+
# all engine routes. Configured handlers short-circuit this and decide for
|
|
48
|
+
# themselves (see SourceMonitor::Security::Authentication).
|
|
49
|
+
def enforce_source_monitor_access_default
|
|
50
|
+
return unless SourceMonitor::Security::Authentication.access_denied_by_default?(self)
|
|
51
|
+
|
|
52
|
+
source_monitor_access_forbidden
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def source_monitor_access_forbidden
|
|
56
|
+
message = "SourceMonitor access is not configured"
|
|
57
|
+
respond_to do |format|
|
|
58
|
+
format.html { render plain: message, status: :forbidden }
|
|
59
|
+
format.turbo_stream do
|
|
60
|
+
render turbo_stream: turbo_stream.append("flash",
|
|
61
|
+
partial: "source_monitor/shared/toast",
|
|
62
|
+
locals: { message: message, level: :error }),
|
|
63
|
+
status: :forbidden
|
|
64
|
+
end
|
|
65
|
+
format.json { render json: { error: message }, status: :forbidden }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
43
69
|
def authenticate_source_monitor_user
|
|
44
70
|
SourceMonitor::Security::Authentication.authenticate!(self)
|
|
45
71
|
end
|
|
@@ -60,22 +86,55 @@ module SourceMonitor
|
|
|
60
86
|
level.to_sym == :error ? TOAST_DURATION_ERROR : TOAST_DURATION_DEFAULT
|
|
61
87
|
end
|
|
62
88
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
89
|
+
# Request flashes are delivered response-local, never broadcast over the
|
|
90
|
+
# global ActionCable notification stream. On full-page HTML loads the layout
|
|
91
|
+
# renders the toasts inline (see +source_monitor_flash_toasts+); on
|
|
92
|
+
# turbo_stream responses we append the toasts to the response body so they
|
|
93
|
+
# reach only the requesting tab.
|
|
94
|
+
def source_monitor_flash_toasts
|
|
95
|
+
payloads = flash_toast_payloads
|
|
96
|
+
# Reading via the layout consumes the flash for this request so it does
|
|
97
|
+
# not linger into the next one.
|
|
98
|
+
flash.discard unless payloads.empty?
|
|
99
|
+
payloads
|
|
100
|
+
end
|
|
66
101
|
|
|
67
|
-
|
|
68
|
-
|
|
102
|
+
def append_flash_toasts_to_turbo_stream
|
|
103
|
+
return unless request.format.turbo_stream?
|
|
104
|
+
return if response.redirect?
|
|
105
|
+
|
|
106
|
+
payloads = flash_toast_payloads
|
|
107
|
+
return if payloads.empty?
|
|
108
|
+
|
|
109
|
+
streams = payloads.map do |payload|
|
|
110
|
+
view_context.turbo_stream.append(
|
|
111
|
+
"source_monitor_notifications",
|
|
112
|
+
partial: "source_monitor/shared/toast",
|
|
113
|
+
locals: {
|
|
114
|
+
message: payload[:message],
|
|
115
|
+
level: payload[:level],
|
|
116
|
+
delay_ms: toast_delay_for(payload[:level])
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
end
|
|
69
120
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
121
|
+
response.body = "#{response.body}#{streams.join}"
|
|
122
|
+
flash.discard
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Builds the list of toast payloads ({ message:, level: }) for the current
|
|
126
|
+
# request's flash. Used by both the inline layout renderer and the
|
|
127
|
+
# turbo_stream after_action so request flashes stay response-local.
|
|
128
|
+
def flash_toast_payloads
|
|
129
|
+
return [] if flash.empty?
|
|
130
|
+
|
|
131
|
+
flash.flat_map do |key, message|
|
|
132
|
+
Array(message).filter_map do |msg|
|
|
133
|
+
next if msg.blank?
|
|
134
|
+
|
|
135
|
+
{ message: msg, level: FLASH_LEVELS[key.to_sym] || :info }
|
|
75
136
|
end
|
|
76
137
|
end
|
|
77
|
-
ensure
|
|
78
|
-
flash.discard
|
|
79
138
|
end
|
|
80
139
|
end
|
|
81
140
|
end
|
|
@@ -33,7 +33,7 @@ module SourceMonitor
|
|
|
33
33
|
|
|
34
34
|
def resolve_source_ids
|
|
35
35
|
if params.dig(:bulk_scrape_enablement, :select_all_pages) == "true"
|
|
36
|
-
|
|
36
|
+
SourceMonitor::Analytics::ScrapeRecommendations.new.candidate_ids
|
|
37
37
|
else
|
|
38
38
|
raw_ids = Array(params.dig(:bulk_scrape_enablement, :source_ids))
|
|
39
39
|
raw_ids.map(&:to_i).reject(&:zero?)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "source_monitor/import_sessions/entry_normalizer"
|
|
4
|
+
|
|
3
5
|
module SourceMonitor
|
|
4
6
|
module ImportSessions
|
|
5
7
|
module BulkConfiguration
|
|
@@ -33,7 +35,7 @@ module SourceMonitor
|
|
|
33
35
|
entry = selected_entries_for_identity.first
|
|
34
36
|
return fallback_identity unless entry
|
|
35
37
|
|
|
36
|
-
normalized =
|
|
38
|
+
normalized = SourceMonitor::ImportSessions::EntryNormalizer.normalize(entry)
|
|
37
39
|
{
|
|
38
40
|
name: normalized[:title].presence || normalized[:feed_url] || fallback_identity[:name],
|
|
39
41
|
feed_url: normalized[:feed_url].presence || fallback_identity[:feed_url],
|