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
4 changes: 2 additions & 2 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
REDIS_URL=redis://redis:6379/0
REDIS_CACHE_URL=redis://redis:6379/1
REDIS_URL=redis://redis:6380/0
REDIS_CACHE_URL=redis://redis:6380/1
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_HOST=database
Expand Down
2 changes: 1 addition & 1 deletion backend/.rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require:
plugins:
- rubocop-factory_bot
- rubocop-capybara
- rubocop-performance
Expand Down
2 changes: 1 addition & 1 deletion backend/.ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.3.1
3.4.8
2 changes: 1 addition & 1 deletion backend/.tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
ruby 3.2.2
ruby 3.4.8
nodejs 20.7.0
113 changes: 113 additions & 0 deletions backend/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# CLAUDE.md

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

## Development Commands

All commands run inside Docker containers:

```bash
# Setup (first time)
cp .env.example .env
docker compose build
docker compose up
docker compose exec web bundle install
docker compose exec web bin/rails db:setup

# Start development
bin/dev # Start server on localhost:3000

# Run tests (note: -P flag needed to include pack specs)
docker compose exec web bundle exec bin/rspec -P ./*/**/*_spec.rb

# Run single test file
docker compose exec web bundle exec bin/rspec spec/requests/api/v1/users_request_spec.rb

# Lint (RuboCop with auto-fix + Reek)
docker compose exec web bundle exec bin/lint

# Security scan (Brakeman)
docker compose exec web bundle exec bin/scan

# Run both lint and security
docker compose exec web bundle exec bin/analyze

# Check modular architecture
docker compose exec web bundle exec bin/packwerk check
```

## Architecture Overview

Rails 8.1.2 API with Ruby 3.4.8, using Docker (PostgreSQL 15, Redis, Elasticsearch 8.10.2, Sidekiq).

### Key Patterns

**ServiceActor Pattern** - Business logic in `app/actors/`. Actors define typed `input`/`output`, use `fail!` for errors:
```ruby
class FetchSchools < Actor
input :school_index_contract
output :data
def call
self.data = fetch_schools
end
end
```

**Dry::Validation Contracts** - Input validation in `app/contracts/`. Extend `ApplicationContract`, raise `InvalidContractError` on failure:
```ruby
UserContracts::Create.call(permitted_params(:email, :password))
```

**Pundit Authorization** - Policies in `app/policies/`. Controllers call `authorize(resource)` and include `after_action :verify_authorized`.

**API Structure** - Controllers inherit from `Api::V1::ApiController` which provides:
- JWT authentication via `devise-api` (`authenticate_devise_api_token!`)
- Standard error handling for `Pundit::NotAuthorizedError`, `ActiveRecord::RecordInvalid`, `InvalidContractError`
- `permitted_params(*keys)` helper for parameter extraction
- PaperTrail audit trail (`set_paper_trail_whodunnit`)

**Packwerk Modular Architecture** - Isolated modules in `packs/` directory (e.g., `demo_pack`, `oauth`). Each pack has its own controllers, actors, specs, and routes module.

### Directory Structure

- `app/actors/` - ServiceActor business logic
- `app/contracts/` - Dry::Validation input contracts
- `app/policies/` - Pundit authorization
- `app/chewy/` - Elasticsearch indices (Chewy)
- `app/sidekiq/` - Background jobs
- `app/graphql/` - GraphQL schema, types, mutations
- `packs/` - Packwerk modular packages

### Testing

RSpec with FactoryBot. Test helpers in `spec/support/`:
- `auth_headers_for(user)` - Generate Bearer token headers for authenticated requests
- `json_response` - Parse response body with indifferent access

Request spec pattern:
```ruby
let(:headers) { auth_headers_for(user) }
before { get "/api/v1/users", headers: }
it { expect(response).to have_http_status(:ok) }
```

### Web Interfaces

- API Docs (Swagger): http://localhost:3000/api-docs/index.html
- GraphQL IDE: http://localhost:3000/graphiql
- Sidekiq Dashboard: http://localhost:3000/sidekiq/
- Admin Dashboard: http://localhost:3000/admin

### Sidekiq Jobs

After creating new jobs, rebuild the sidekiq container:
```bash
docker compose stop
docker compose up --build
```

### Code Style

- Double quotes for strings
- Uses `frozen_string_literal: true` pragma
- RuboCop extensions: rubocop-rails, rubocop-rspec, rubocop-graphql, rubocop-performance
2 changes: 1 addition & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ FROM base as build

# Install packages needed to build gems
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libpq-dev libvips pkg-config
apt-get install --no-install-recommends -y build-essential git libpq-dev libvips pkg-config libyaml-dev

# RUN apt-get update -qq && apt-get install -y postgresql-client

Expand Down
28 changes: 18 additions & 10 deletions backend/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby "3.3.1"
ruby "3.4.8"

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.1.3"
gem "rails", "~> 8.0"

# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem "sprockets-rails"
Expand All @@ -15,7 +15,7 @@ gem "sprockets-rails"
gem "pg"

# Use the Puma web server [https://github.com/puma/puma]
gem "puma", "~> 5.0"
gem "puma", "~> 6.0"

# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails"
Expand All @@ -32,6 +32,12 @@ gem "jbuilder"
# Use Redis adapter to run Action Cable in production
gem "redis"

# Pin connection_pool to 2.x for Rails 7.1 compatibility
gem "connection_pool", "~> 2.4"

# CSV support (removed from stdlib in Ruby 3.4)
gem "csv"

# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
# gem "kredis"

Expand All @@ -51,7 +57,7 @@ gem "bootsnap", require: false
# gem "image_processing", "~> 1.2"

# Devise is a flexible authentication solution for Rails based on Warden [https://github.com/heartcombo/devise]
gem "devise", "~> 4.9"
gem "devise", "~> 5.0"

# devise-api authenticate API requests [https://github.com/nejdetkadir/devise-api]
gem "devise-api", github: "nejdetkadir/devise-api", branch: "main"
Expand Down Expand Up @@ -128,11 +134,13 @@ end

group :development do
# To ensure code consistency [https://docs.rubocop.org]
gem "rubocop", "1.56.2"
gem "rubocop-graphql", "~> 1.4"
gem "rubocop-performance", "1.19.0"
gem "rubocop-rails", "2.20.2"
gem "rubocop-rspec", "2.23.2"
gem "rubocop", "~> 1.69"
gem "rubocop-capybara", "~> 2.21"
gem "rubocop-factory_bot", "~> 2.26"
gem "rubocop-graphql", "~> 1.5"
gem "rubocop-performance", "~> 1.23"
gem "rubocop-rails", "~> 2.27"
gem "rubocop-rspec", "~> 3.3"
# Use console on exceptions pages [https://github.com/rails/web-console]
gem "web-console"

Expand All @@ -151,7 +159,7 @@ end
group :test do
# Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
gem "capybara"
gem "rspec-rails", "~> 6.0.0"
gem "rspec-rails", "~> 7.0"
gem "rspec-sidekiq"
gem "selenium-webdriver"
gem "shoulda-matchers", "~> 5.0"
Expand Down
Loading