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
19 changes: 11 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,16 @@ jobs:
test:
runs-on: ubuntu-latest

# services:
# redis:
# image: valkey/valkey:8
# ports:
# - 6379:6379
# options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
services:
postgres:
image: postgres:16
ports:
- 5432:5432
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

steps:
- name: Checkout code
uses: actions/checkout@v6
Expand All @@ -71,6 +75,5 @@ jobs:
- name: Run tests
env:
RAILS_ENV: test
# RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
# REDIS_URL: redis://localhost:6379/0
DATABASE_URL: postgres://postgres:postgres@localhost:5432/test
run: bin/rails db:test:prepare test
8 changes: 3 additions & 5 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# Omakase Ruby styling for Rails
inherit_gem: { rubocop-rails-omakase: rubocop.yml }

# Overwrite or add rules to create your own house style
#
# # Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
# Layout/SpaceInsideArrayLiteralBrackets:
# Enabled: false
# Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
Layout/SpaceInsideArrayLiteralBrackets:
Enabled: false
38 changes: 38 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# CLAUDE.md

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

## Build and Test Commands

- **Run all tests:** `bin/rails test`
- **Run a single test file:** `bin/rails test test/models/video_test.rb`
- **Run a specific test:** `bin/rails test test/models/video_test.rb:10` (line number)
- **Start development server:** `bin/rails server`
- **Database setup:** `bin/rails db:create db:migrate`
- **Lint:** `bin/rubocop` (uses rubocop-rails-omakase)
- **Security scan:** `bin/brakeman`

## Architecture

This is a Rails 8.1 API backend deployed on Heroku. It serves both a JSON API (for a separate frontend client) and minimal web UI pages.

### Dual Controller Structure

- `ApplicationController` (inherits `ActionController::Base`) - for web UI with full Rails features
- `Api::ApplicationController` (inherits `ActionController::API`) - for JSON API endpoints
- Both include the `Authentication` concern for token-based auth

### API Authentication

Token-based authentication via Bearer tokens in the Authorization header. Sessions are stored in the database (`Session` model) and linked to users. The `Authentication` concern handles session lookup via `find_session_by_token`.

### Core Domain Models

- `Video` - YouTube videos identified by `youtube_id`
- `Summary` - User-generated summaries of videos, scoped to user
- `AmazonLink` - Product links extracted from video descriptions; automatically resolves amzn.to short URLs on save
- `VideoAmazonLink` - Join table connecting videos to amazon links (many-to-many)

### Routes

API routes use `scope module: :api` to namespace controllers without URL prefix. Web routes are at root level.
65 changes: 2 additions & 63 deletions test/controllers/passwords_controller_test.rb
Original file line number Diff line number Diff line change
@@ -1,67 +1,6 @@
require "test_helper"

class PasswordsControllerTest < ActionDispatch::IntegrationTest
setup { @user = User.take }

test "new" do
get new_password_path
assert_response :success
end

test "create" do
post passwords_path, params: { email_address: @user.email_address }
assert_enqueued_email_with PasswordsMailer, :reset, args: [ @user ]
assert_redirected_to new_session_path

follow_redirect!
assert_notice "reset instructions sent"
end

test "create for an unknown user redirects but sends no mail" do
post passwords_path, params: { email_address: "missing-user@example.com" }
assert_enqueued_emails 0
assert_redirected_to new_session_path

follow_redirect!
assert_notice "reset instructions sent"
end

test "edit" do
get edit_password_path(@user.password_reset_token)
assert_response :success
end

test "edit with invalid password reset token" do
get edit_password_path("invalid token")
assert_redirected_to new_password_path

follow_redirect!
assert_notice "reset link is invalid"
end

test "update" do
assert_changes -> { @user.reload.password_digest } do
put password_path(@user.password_reset_token), params: { password: "new", password_confirmation: "new" }
assert_redirected_to new_session_path
end

follow_redirect!
assert_notice "Password has been reset"
end

test "update with non matching passwords" do
token = @user.password_reset_token
assert_no_changes -> { @user.reload.password_digest } do
put password_path(token), params: { password: "no", password_confirmation: "match" }
assert_redirected_to edit_password_path(token)
end

follow_redirect!
assert_notice "Passwords did not match"
end

private
def assert_notice(text)
assert_select "div", /#{text}/
end
# Password reset routes are not currently exposed.
# Add tests here when password routes are added to config/routes.rb
end
32 changes: 15 additions & 17 deletions test/controllers/sessions_controller_test.rb
Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
require "test_helper"

class SessionsControllerTest < ActionDispatch::IntegrationTest
setup { @user = User.take }

test "new" do
get new_session_path
assert_response :success
end
setup { @user = users(:one) }

test "create with valid credentials" do
post session_path, params: { email_address: @user.email_address, password: "password" }
post session_path, params: { email_address: @user.email_address, password: "password" }, as: :json

assert_redirected_to root_path
assert cookies[:session_id]
assert_response :success
json = JSON.parse(response.body)
assert json["token"].present?
assert_equal @user.email_address, json["email"]
end

test "create with invalid credentials" do
post session_path, params: { email_address: @user.email_address, password: "wrong" }
post session_path, params: { email_address: @user.email_address, password: "wrong" }, as: :json

assert_redirected_to new_session_path
assert_nil cookies[:session_id]
assert_response :unauthorized
json = JSON.parse(response.body)
assert_equal "Invalid email or password", json["error"]
end

test "destroy" do
sign_in_as(User.take)

delete session_path
session = @user.sessions.create!
delete session_path, headers: { "Authorization" => "Bearer #{session.token}" }, as: :json

assert_redirected_to new_session_path
assert_empty cookies[:session_id]
assert_response :success
json = JSON.parse(response.body)
assert_equal "Logged out", json["message"]
end
end
4 changes: 3 additions & 1 deletion test/fixtures/amazon_links.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html

one:
summary: one
url: https://www.amazon.com/dp/B08N5WRWNW

two:
url: https://www.amazon.com/dp/B09V3KXJPB
8 changes: 2 additions & 6 deletions test/fixtures/summaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

one:
user: one
video_id: MyString
video_title: MyString
video: one
summary_text: MyText
video_url: MyString

two:
user: two
video_id: MyString
video_title: MyString
video: two
summary_text: MyText
video_url: MyString
12 changes: 6 additions & 6 deletions test/fixtures/videos.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html

one:
youtube_id: MyString
title: MyString
url: MyString
youtube_id: abc123
title: Video One
url: https://www.youtube.com/watch?v=abc123

two:
youtube_id: MyString
title: MyString
url: MyString
youtube_id: def456
title: Video Two
url: https://www.youtube.com/watch?v=def456
2 changes: 1 addition & 1 deletion test/models/amazon_link_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

class AmazonLinkTest < ActiveSupport::TestCase
test "resolves amzn.to short URL to amazon.com on save" do
amazon_link = AmazonLink.new(summary: summaries(:one), url: "https://amzn.to/2GlSvyy")
amazon_link = AmazonLink.new(url: "https://amzn.to/2GlSvyy")
amazon_link.save

assert_includes amazon_link.url, "amazon.com"
Expand Down