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
9 changes: 9 additions & 0 deletions .github/workflows/specs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ jobs:
# pinned in Gemfile.lock (BUNDLED WITH).
bundler-cache: true

- name: Set up Node
uses: actions/setup-node@v5
with:
node-version: 20
cache: npm

- name: Install the Castle browser SDK
run: npm ci

- name: Set up the test database
run: |
cp config/database.yml.example config/database.yml
Expand Down
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# syntax=docker/dockerfile:1

# Fetch the Castle browser SDK from npm (served at runtime from node_modules).
FROM node:20-slim AS frontend
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci

# ---------- Build stage ----------
FROM ruby:3.4.9-slim AS build

Expand All @@ -21,6 +27,7 @@ RUN gem install bundler -v "${BUNDLER_VERSION}" && \
rm -rf "${BUNDLE_PATH}"/ruby/*/cache

COPY . .
COPY --from=frontend /app/node_modules/@castleio/castle-js/dist ./node_modules/@castleio/castle-js/dist

# The real database.yml is environment-specific and git-ignored; derive it from
# the committed example so the build is reproducible from a clean checkout.
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ cd castle-ruby-example
bundle install
```

The Castle browser SDK is served at runtime straight from `node_modules`, so
install it too:

```bash
npm install
```

Configure your environment and database:

```bash
Expand Down
28 changes: 28 additions & 0 deletions app/controllers/vendor/castle_js_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module Vendor
# Serves the Castle browser SDK from the npm install (node_modules).
class CastleJsController < ActionController::Base
DIST = Rails.root.join('node_modules/@castleio/castle-js/dist')

skip_forgery_protection

def show
path = resolved_file
return head :not_found unless path

send_file path, type: 'application/javascript', disposition: 'inline'
end

private

def resolved_file
root = DIST.expand_path
candidate = root.join(params[:filename].to_s).expand_path
return unless candidate.to_s.start_with?("#{root}#{File::SEPARATOR}")
return unless candidate.file?

candidate
end
end
end
2 changes: 1 addition & 1 deletion app/views/layouts/_castle_js.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<% if ENV["CASTLE_PK"].present? %>
<%# Castle browser SDK. Mints the request token that ties the browser to the %>
<%# server-side risk/filter calls. See app/assets/javascripts/castle.js. %>
<script src="https://cdn.jsdelivr.net/npm/@castleio/castle-js@2/dist/castle.browser.js"></script>
<script src="/vendor/castle-js/castle.browser.js"></script>
<script>
if (window.Castle) {
Castle.configure({ pk: "<%= j ENV['CASTLE_PK'] %>" });
Expand Down
3 changes: 3 additions & 0 deletions bin/setup
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ chdir APP_ROOT do
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')

puts "\n== Installing the Castle browser SDK =="
system! 'npm install'

puts "\n== Copying sample files =="
unless File.exist?('config/database.yml')
cp 'config/database.yml.example', 'config/database.yml'
Expand Down
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@
resources :castle_webhooks, only: %i[index create]
end

get '/vendor/castle-js/*filename', to: 'vendor/castle_js#show'

root to: 'main#index'
end
22 changes: 22 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "castle-ruby-example",
"version": "1.0.0",
"private": false,
"description": "A Rails app demonstrating key Castle workflows on top of the Castle Ruby SDK.",
"repository": {
"type": "git",
"url": "git+https://github.com/castle/castle-ruby-example.git"
},
"license": "MIT",
"dependencies": {
"@castleio/castle-js": "^2.8.4"
}
}
24 changes: 24 additions & 0 deletions spec/controllers/vendor/castle_js_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

RSpec.describe Vendor::CastleJsController do
describe 'GET #show' do
it 'returns 404 for a missing file' do
get :show, params: { filename: 'nope.js' }

expect(response).to have_http_status(:not_found)
end

it 'returns 404 when the path escapes the dist directory' do
get :show, params: { filename: '../package.json' }

expect(response).to have_http_status(:not_found)
end

it 'serves castle.browser.js from the npm install' do
get :show, params: { filename: 'castle.browser.js' }

expect(response).to have_http_status(:ok)
expect(response.media_type).to eq('application/javascript')
end
end
end