The Rails Application
The Rails 8.1 application (rails_web/) is the control plane of Brand Protect. It owns user accounts, the brand profile, scan orchestration, persistence of findings, PDF storage, and the browser-facing dashboard.
- Ruby 3.4, Rails 8.1, Hotwire (turbo-rails + stimulus-rails), importmap-rails, propshaft.
- PostgreSQL 16 with
pg_trgm. - Solid Queue for background jobs (production), Solid Cable for ActionCable, Solid Cache.
- Tailwind CSS 4 for styling.
- Sentry (
sentry-ruby/sentry-rails) for error tracking. - Auth is custom — cookie sessions with
bcrypt, no Devise.
Controllers
Section titled “Controllers”| Controller | Actions | Purpose |
|---|---|---|
ApplicationController |
— | Base; auth concern |
DashboardController |
show, start_scan, stop_scan, resume_scan, status, campaign_narratives |
Main dashboard + scan lifecycle + JSON poll + LLM narratives |
TriageController |
index, feedback, bulk_feedback |
Analyst review queue |
AccountController |
edit, update |
Brand profile form |
ReportsController |
show |
Serves scan PDFs (?variant=summary) |
TakedownsController |
generate |
Takedown email draft |
MediaController |
screenshot, avatar |
Owner-only PNG files |
SessionsController / RegistrationsController / PasswordsController |
— | Auth |
Admin::BaseController |
require_admin |
Admin gate |
Admin::UsersController |
CRUD | User registry |
Admin::SettingsController |
show, update |
AI provider + audit config |
V1::InternalApiController |
— | Token-authenticated Python→Rails API |
The scan lifecycle
Section titled “The scan lifecycle”ScanOrchestrator (app/services/scan_orchestrator.rb) builds the payload and dispatches to Python:
start_scan!creates aScanRun, merges user + derived keywords, includes the enricheddomain_profilessnapshot, and POSTs to/v1/scan/start(expecting 202).- If domain enrichment is still pending, the dispatch is deferred until profiles complete.
resume_scanre-dispatches withresume_from_stage:for terminal scans.stop_scanmarks the runcancelled, clears the Redis active key, and broadcasts acancelcommand.
Scan status values: pending, queued, running, completed, failed, cancelled.
Background jobs
Section titled “Background jobs”| Job | Trigger | Purpose |
|---|---|---|
EnrichDomainJob |
OwnedDomain create/update |
POST /domain/enrich |
EnrichSocialHandleJob |
SocialHandle create/update |
POST /social/enrich |
ExpandKeywordsJob |
Profile update | LLM keyword inference; rate-limited (4/30 min) |
ReportGenerationJob |
Scan completion | Fetch PDFs from Python, attach to ScanReport, enqueue webhook |
WebhookDeliveryJob |
Scan completion/failure | Deliver scan snapshot + PDFs to webhook_url |
WeeklyRescanJob |
recurring.yml / dev scheduler |
Scheduled rescans |
The Redis pub/sub consumer
Section titled “The Redis pub/sub consumer”config/initializers/scan_db_consumer.rb runs a thread subscribing to the brand_protect_cable:* channels:
process_scan_update— merges stage progress intostage_states, broadcasts ActionCable progress, maintains thescan:active:<id>key (TTL 1h), and on terminal status writes the audit event, setscompleted_at, and enqueues report/webhook jobs.process_initial_findings/process_findings_deltas— upsertFindingrows (initial batch keyed byoriginal_domain, deltas bydomain).process_domain_profile/process_social_profile— upsert enrichment results with generation-staleness guards; dispatch deferred scans when domain enrichment completes.process_initial_social_findings/process_social_findings_deltas— upsertSocialFindingrows by(platform, handle).
Authentication
Section titled “Authentication”Authenticationconcern:require_authentication→Current.sessionfromcookies.signed[:session_id](httponly, same_site: lax, permanent). Each login creates aSessionrow.- Signup is open and auto-logs-in.
- Password reset via
PasswordsMailerwith a signed token; rate-limited. - Admin:
User#admin?gatesAdmin::BaseController. Admins can start scans and edit profiles for any user. - Internal API: Bearer token vs
ENV["INTERNAL_API_TOKEN"]via constant-time comparison; fails closed.
Dashboard JavaScript
Section titled “Dashboard JavaScript”scan_monitor_controller.js is the core dashboard controller:
- Subscribes to
ScanChannel(ActionCable) and pollsdashboard_statusevery 2s. - Handles message types:
findings,log,phase_progress,phase_update,complete, plus genericapplyUpdateforfailed/cancelled. - Renders the findings table entirely in JS: ~45 sortable columns, text filter, pagination (15/page), column-visibility dropdown, resizable columns, and expandable debug rows.
- Renders the social findings table (10/page), switches Domains/Social tabs, and drives Start/Stop/Resume controls.
Other Stimulus controllers: bulk_triage, takedown, campaign_narratives, nested_list, sidebar, toast.