Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.4.4
4.0.0
242 changes: 242 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

WhyRuby.info is a Ruby advocacy community website built with Rails 8.1 (edge/main branch) and the Solid Stack (SQLite, SolidQueue, SolidCache, SolidCable). It features a universal content model supporting articles, links, and success stories with AI-generated summaries, GitHub OAuth authentication, and community-driven content moderation.

## Development Commands

### Server Management
```bash
# Start the development server (REQUIRED - do not use rails server directly)
bin/dev

# Monitor logs in real-time
tail -f log/development.log
```

**IMPORTANT**: Always use `bin/dev` instead of `rails server`. This starts both the Rails server (port 3003) and Tailwind CSS watcher via Procfile.dev.

### Database
```bash
# Create and setup database
rails db:create
rails db:migrate
rails db:seed

# Database console
rails db

# Promote user to admin
rails runner "User.find_by(username: 'github-username').update!(role: :admin)"
```

### Testing & Code Quality
```bash
# Run all tests
rails test

# Run single test file
rails test test/models/post_test.rb

# Run single test
rails test test/models/post_test.rb:line_number

# Code style (auto-fixes and stages changes via lefthook)
bundle exec rubocop
bundle exec rubocop -A # Auto-correct issues
```

### Rails Console & Generators
```bash
# Console
rails console

# Generate model with ULID primary key
rails generate model ModelName

# Other generators
rails generate controller ControllerName
rails generate migration MigrationName
```

## Architecture & Key Patterns

### Data Model Structure

The application uses a **Universal Content Model** where the `Post` model handles three distinct content types via the `post_type` enum:

- **Articles**: Original content with markdown (`content` field required, `url` must be blank)
- **Links**: External content (`url` required, `content` must be blank)
- **Success Stories**: Ruby success stories with SVG logo (`logo_svg` and `content` required, `url` must be blank)

**Key Models:**
- `User`: GitHub OAuth authentication, role-based access (member/admin), trusted user system based on contribution counts (3+ published posts, 10+ published comments)
- `Post`: Universal content model with FriendlyId slugs, soft deletion via `archived` flag, counter caches for reports
- `Category`: Content organization with position ordering, special success story category flag
- `Tag`: HABTM relationship with posts
- `Comment`: User feedback on posts with published flag
- `Report`: Content moderation by trusted users, auto-hides after 3+ reports

### Primary Keys & IDs

**All tables use ULID instead of integer IDs:**
```ruby
create_table :table_name, force: true, id: false do |t|
t.primary_key :id, :string, default: -> { "ULID()" }
# ...
end
```

This is configured via the `sqlite-ulid` gem and provides better distribution and sortability.

### Authentication & Authorization

- **Authentication**: GitHub OAuth only via Devise + OmniAuth
- **Roles**: `member` (default) and `admin` (enum in User model)
- **Trusted Users**: Automatically qualified when `published_posts_count >= 3` AND `published_comments_count >= 10`
- **Permissions**: Only trusted users can report content; only admins can access `/admin` (Avo panel)

### Content Moderation Flow

1. Trusted users can report inappropriate content
2. After 3+ reports, content is automatically:
- Set to `published: false`
- Flagged with `needs_admin_review: true`
- Admin notification sent via `NotifyAdminJob`
3. Admins review and restore or permanently archive via Avo panel

### Background Jobs (SolidQueue)

- `GenerateSummaryJob`: Creates AI summaries for new/updated posts using OpenAI or Anthropic APIs
- `GenerateSuccessStoryImageJob`: Generates OG images for success stories from SVG logos
- `NotifyAdminJob`: Sends notifications when content is auto-hidden
- `UpdateGithubDataJob`: Refreshes user GitHub data (repositories, stars, etc.)

### Image Processing

Posts support `featured_image` attachments via ActiveStorage. Images are processed into multiple variants (small, medium, large, og) and stored as separate blobs with metadata in `image_variants` JSON column. Processing uses the `ImageProcessor` service.

### URL Routing Pattern

The application uses a **catch-all routing strategy** for clean URLs:

```
/:category_slug # Category pages
/:category_slug/:post_slug # Post pages
```

**Important**: Category and post routes use constraints and are defined at the END of routes.rb to avoid conflicts with explicit routes like `/admin`, `/community`, `/legal/*`, etc.

### Services Layer

Service objects in `app/services/` handle complex operations:
- `GithubDataFetcher`: Fetches and updates user GitHub profile data and repositories on sign-in
- `ImageProcessor`: Processes featured images into multiple variants (small, medium, large, og)
- `SuccessStoryImageGenerator`: Generates OG images for success stories from SVG logos
- `SvgSanitizer`: Sanitizes SVG content to prevent XSS attacks

### FriendlyId Implementation

Both `User` and `Post` models use FriendlyId with history:
- `User`: Slugs from `username`
- `Post`: Slugs from `title`

Both models implement `create_slug_history` to manually save old slugs when changed, ensuring historical URLs redirect properly.

## Rails 8 Specific Patterns

### Solid Stack Usage

- **SolidQueue**: Default background job processor (no Redis needed)
- **SolidCache**: Database-backed caching
- **SolidCable**: WebSocket functionality via SQLite
- Configuration files: `config/queue.yml`, `config/cache.yml`, `config/cable.yml`

### Modern Rails 8 Conventions

- Use `params.expect()` for parameter handling instead of strong parameters (Rails 8.1 feature)
- Propshaft for asset pipeline (not Sprockets)
- Tailwind CSS 4 via `tailwindcss-rails` gem
- Hotwire (Turbo + Stimulus) for frontend interactivity

### Migrations

Always use ULID primary keys. Never use auto-increment integers:

```ruby
create_table :posts, force: true, id: false do |t|
t.primary_key :id, :string, default: -> { "ULID()" }
t.string :title, null: false
# ...
end
```

## Important Development Practices

### Server & Logging

- **ALWAYS** use `bin/dev` to start the server
- Check logs after every significant change
- Monitor `log/development.log` for errors and performance issues
- Review logs before considering any change complete

### Code Style

The project uses lefthook to automatically run RuboCop before commits:
- RuboCop runs with auto-fix (`-A`) on all files
- Fixed files are automatically staged
- Configuration in `.lefthook.yml` and `.rubocop.yml`

### Testing

- Framework: Minitest (Rails default)
- Fixtures in `test/fixtures/`
- Test structure: `test/models/`, `test/controllers/`, `test/system/`
- Run tests before committing

## Credentials & Configuration

### Development Credentials

```bash
rails credentials:edit --environment development
```

Required credentials:
```yaml
github:
client_id: your_github_oauth_app_client_id
client_secret: your_github_oauth_app_client_secret

openai:
api_key: your_openai_api_key # Optional - for AI summaries
```

### GitHub OAuth Setup

1. Create OAuth App at: https://github.com/settings/developers
2. Set callback URL: `http://localhost:3000/users/auth/github/callback`
3. Add credentials to development credentials file

### Deployment

- Configured for Kamal 2 deployment (see `config/deploy.yml`)
- Litestream for SQLite backups to S3 (see `config/litestream.yml`)
- RorVsWild for performance monitoring (see `config/rorvswild.yml`)

## Admin Panel

- Admin panel powered by Avo (v3.2+)
- Route: `/admin` (only accessible to admin users)
- Configuration: `app/avo/` directory
- Litestream backup UI: `/litestream` (admin only)

## Markdown Rendering

- Redcarpet for markdown parsing
- Rouge for syntax highlighting
- Markdown content stored in `Post#content` field for articles and success stories
- Custom helper: `ApplicationHelper#render_markdown_with_syntax_highlighting`
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.4.4
ARG RUBY_VERSION=4.0.0
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base

# Rails app lives here
Expand Down
3 changes: 0 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ gem "kamal", require: false
# Add HTTP asset caching/compression and X-Sendfile acceleration to Puma [https://github.com/basecamp/thruster/]
gem "thruster", require: false

# Fetch metadata from URLs
gem "metainspector"

# Authentication
gem "devise", "~> 4.9"
gem "omniauth-github", "~> 2.0"
Expand Down
Loading