Skip to content

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.
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

ScanOrchestrator (app/services/scan_orchestrator.rb) builds the payload and dispatches to Python:

  1. start_scan! creates a ScanRun, merges user + derived keywords, includes the enriched domain_profiles snapshot, and POSTs to /v1/scan/start (expecting 202).
  2. If domain enrichment is still pending, the dispatch is deferred until profiles complete.
  3. resume_scan re-dispatches with resume_from_stage: for terminal scans.
  4. stop_scan marks the run cancelled, clears the Redis active key, and broadcasts a cancel command.

Scan status values: pending, queued, running, completed, failed, cancelled.

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

config/initializers/scan_db_consumer.rb runs a thread subscribing to the brand_protect_cable:* channels:

  • process_scan_update — merges stage progress into stage_states, broadcasts ActionCable progress, maintains the scan:active:<id> key (TTL 1h), and on terminal status writes the audit event, sets completed_at, and enqueues report/webhook jobs.
  • process_initial_findings / process_findings_deltas — upsert Finding rows (initial batch keyed by original_domain, deltas by domain).
  • 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 — upsert SocialFinding rows by (platform, handle).
  • Authentication concern: require_authenticationCurrent.session from cookies.signed[:session_id] (httponly, same_site: lax, permanent). Each login creates a Session row.
  • Signup is open and auto-logs-in.
  • Password reset via PasswordsMailer with a signed token; rate-limited.
  • Admin: User#admin? gates Admin::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.

scan_monitor_controller.js is the core dashboard controller:

  • Subscribes to ScanChannel (ActionCable) and polls dashboard_status every 2s.
  • Handles message types: findings, log, phase_progress, phase_update, complete, plus generic applyUpdate for failed/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.