The Rails app behind chillfiltr.com: a curated music and literature blog with paid song submissions via Stripe and automated publishing of The Weekly Catch, Krister Axel's Wednesday-night show on KSKQ (89.5 FM Ashland, 94.1 FM Medford).
The archive runs from 1999 to today: roughly 2,200 posts by 35 authors, sorted
into six topics (music, lifestyle, personal, technology, prose, poetry) plus
radio for Weekly Catch episodes, with about 90 tags and location labels.
- Blog: infinite-scroll index, topic filters, tags, author pages, search, and a monthly archive. Posts can embed Vimeo, YouTube, or Mixcloud players.
- Radio page: the latest Weekly Catch episode plays in a Mixcloud widget, with a paginated list of past episodes (4 per page, 16 max).
- Song submissions: artists upload tracks straight to S3 and pay $3 per song through Stripe Checkout. See docs/song_submissions.md.
- Contact form: a native form on
/pages/contactthat emails info@chillfiltr.com through the same Brevo relay, with the visitor's address as Reply-To. Each message is also saved incontact_messages;delivered_atis stamped when the email goes out, soContactMessage.undeliveredlists any that were saved but never sent. Admins can read them at/admin/contact_messages(also in the menu under More), with a filter for the unsent ones. Spam defenses: a hidden honeypot field, a signed form timestamp (submissions faster than 3 seconds, or without a valid token, are discarded, not saved), and a limit of 5 messages per hour per IP. Messages with 3 or more links are still saved and emailed, but markedsuspected_spamand prefixed "[Possible spam]" in the subject. - Accounts: email/password sign-in plus Google and Apple via OmniAuth.
- RSS: feeds for all posts or a single topic.
| Ruby | 3.4.1 |
| Rails | 8.0 |
| Database | PostgreSQL (separate databases for app, Solid Queue, Solid Cache, Solid Cable) |
| Web server | Puma behind Thruster |
| Front end | Hotwire (Turbo + Stimulus), importmap, Tailwind CSS 4 |
| Background jobs | Solid Queue (runs inside Puma when SOLID_QUEUE_IN_PUMA=true) |
| File storage | Active Storage on Amazon S3 |
| Payments | Stripe Checkout + webhooks |
| Brevo SMTP | |
| Hosting | Render (Docker), Oregon region |
Design: Instrument Serif + Karla, with the "Open Air" palette defined as
Tailwind theme tokens in app/assets/tailwind/application.css.
git clone git@github.com:kaxel/chillonrails.git
cd chillonrails
bin/setup # installs gems, creates and migrates the database
bin/dev # Rails server + Tailwind watcher (http://localhost:3000)You need Ruby 3.4.1 and a local PostgreSQL server. Run bin/rails db:seed or
restore a dump if you want content to browse.
Production settings live in the Render dashboard (names are listed in
render.yaml):
| Variable | Purpose |
|---|---|
DATABASE_URL |
PostgreSQL connection |
RAILS_MASTER_KEY |
decrypts config/credentials.yml.enc |
WEB_CONCURRENCY |
Puma workers |
SOLID_QUEUE_IN_PUMA |
set to true so jobs run in the web process |
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET |
song submission checkout |
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_S3_BUCKET |
uploaded audio |
Locally, use .env (not committed) and Stripe test keys.
bundle exec rspec # test suite
bin/rubocop # style (rails-omakase)
bin/brakeman --no-pager # security scan
bin/importmap audit # JavaScript dependency auditGitHub Actions (.github/workflows/ci.yml) runs Brakeman, the importmap
audit, and RuboCop on every push and pull request.
Render builds the Dockerfile and runs bin/rails db:migrate before each
release. Automatic deploys are off, so deploy manually from the Render
dashboard after pushing to main.
Episodes are stored as radio posts and imported from
db/data/weekly_catch_episodes.json.
- In the
spinitscraperepo, add the new setlist, then run./export_weekly_catch_to_chillfiltr.rb. It rewrites the JSON and adds the episode's thumbnail topublic/weekly-catch-fish/. - Run
bin/rails weekly_catch:importlocally and check the radio page. - Commit and push both files, deploy, then run
bin/rails weekly_catch:importon Render. The radio page's "Now Playing" section switches to the newest episode automatically.
The import is safe to re-run: it creates missing episodes and backfills the Mixcloud link, image, and tracklist on existing ones.
config/recurring.yml runs PurgePendingSubmissionsJob daily at 4am,
removing submissions that were never paid for after 24 hours.