redis-server
rails s
bundle exec sidekiq -c 1 -q default -q mailers
TODO:
Ensure credentials are set in Rails credentials
Create new buckets on AWS S3
modified: Gemfile
modified: Gemfile.lock
modified: README.md
modified: config/credentials.yml.enc
modified: config/sitemap.rb
Resources:
- https://edgeguides.rubyonrails.org/security.html#custom-credentials
- https://s3.console.aws.amazon.com/
- https://blog.saeloun.com/2019/10/10/rails-6-adds-support-for-multi-environment-credentials.html
Commands
rails credentials:help
rails credentials:edit
rails credentials:edit --environment production
EDITOR=nano rails credentials:edit
cat config/credentials/production.key
heroku config:set RAILS_MASTER_KEY=`cat config/master.key`
Setup
Create user in IAM
Create bucket in S3
Usage
Rails.application.credentials.config
Rails.application.credentials.aws[:access_key_id]
Resources:
bundle install
rake sitemap:install
rake sitemap:refresh
rake sitemap:refresh:no_ping
modified: Gemfile
modified: Gemfile.lock
modified: README.md
modified: app/controllers/posts_controller.rb
new file: app/models/concerns/posts/scopes.rb
modified: app/models/post.rb
new file: config/sitemap.rb
new file: public/sitemap.xml
Resources:
# Perform background queue jobs
# https://github.com/mperham/sidekiq
gem 'sidekiq'
bundle install
modified: Gemfile
modified: Gemfile.lock
modified: Procfile
modified: README.md
modified: app/controllers/api/v1/posts_controller.rb
modified: app/controllers/sessions_controller.rb
modified: app/models/user.rb
modified: app/serializers/post_show_serializer.rb
modified: config/application.rb
config/initializers/redis.rb
config/initializers/sidekiq.rb
modified: README.md
modified: app/controllers/api/v1/comments_controller.rb
modified: app/models/concerns/abilities/commentable.rb
modified: app/models/concerns/comments/logic.rb
modified: app/models/concerns/posts/hooks.rb
modified: app/models/post.rb
modified: app/policies/comment_policy.rb
new file: app/serializers/comment_for_post_show_serializer.rb
modified: app/serializers/comment_index_serializer.rb
new file: app/serializers/comment_show_serializer.rb
modified: app/serializers/post_index_serializer.rb
modified: app/serializers/post_show_serializer.rb
modified: spec/requests/comments_request_spec.rb
modified: spec/requests/posts_request_spec.rb
modified: spec/support/object_creators.rb
rails g migration createComments
rails db:migrate
files
# modified: README.md
# new file: app/controllers/api/v1/comments_controller.rb
# new file: app/models/comment.rb
# new file: app/models/concerns/abilities/commentable.rb
# new file: app/models/concerns/comments/associations.rb
# new file: app/models/concerns/comments/logic.rb
# new file: app/models/concerns/comments/validations.rb
# modified: app/models/concerns/users/associations.rb
# new file: app/models/concerns/users/logic.rb
# modified: app/models/post.rb
# modified: app/models/user.rb
# new file: app/policies/comment_policy.rb
# new file: app/serializers/comment_index_serializer.rb
# modified: app/serializers/post_index_serializer.rb
# modified: app/serializers/post_show_serializer.rb
# modified: config/locales/en.yml
# modified: config/routes.rb
# new file: db/migrate/20210422213742_create_comments.rb
# modified: db/schema.rb
# new file: spec/requests/comments_request_spec.rb
# modified: spec/support/object_creators.rb
rails g migration addSlugToPosts
FILES
modified: app/controllers/api/v1/posts_controller.rb
modified: app/models/concerns/users/validations.rb
modified: app/models/post.rb
modified: app/models/user.rb
modified: app/serializers/post_show_serializer.rb
modified: config/locales/en.yml
modified: config/routes.rb
new file: db/migrate/20210422212211_add_slug_to_posts.rb
modified: db/schema.rb
modified: spec/requests/posts_request_spec.rb
Post.find_each(&:save)
rails g migration AddSlugToUsers slug:uniq
Files
# modified: Gemfile
# modified: Gemfile.lock
# modified: README.md
# modified: app/controllers/api/v1/posts_controller.rb
# modified: app/controllers/api/v1/users_controller.rb
# modified: app/controllers/application_controller.rb
# modified: app/models/concerns/posts/logic.rb
# modified: app/models/concerns/posts/validations.rb
# modified: app/models/user.rb
# modified: app/policies/post_policy.rb
# modified: app/serializers/post_index_serializer.rb
# new file: app/serializers/user_show_serializer.rb
# new file: config/initializers/friendly_id.rb
# modified: config/routes.rb
# new file: db/migrate/20210414181412_add_slug_to_users.rb
# modified: db/schema.rb
# modified: spec/requests/posts_request_spec.rb
# modified: spec/requests/users_request_spec.rb
#
After, rails console:
User.find_each(&:save)
Gemfile
app/controllers/api/v1/posts_controller.rb
app/controllers/application_controller.rb
app/models/post.rb
app/models/concerns/posts/logic.rb
app/policies/application_policy.rb
app/policies/post_policy.rb
app/serializers/post_index_serializer.rb
app/serializers/post_show_serializer.rb
config/locales/en.yml
config/routes.rb
spec/requests/posts_request_spec.rb
spec/support/object_creators.rb
rails g migration createPosts
rake db:migrate
Files
db/migrations/XXX_create_posts.rb
app/models/post.rb
app/models/user.rb
app/models/concerns/posts/associations.rb
app/models/concerns/posts/validations.rb
app/models/concerns/users/associations.rb
app/models/concerns/users/validations.rb
config/routes.rb
spec/requests/posts_request_spec.rb
spec/support/object_creators.rb
Backend, and why using localStorage: waiting-for-dev/devise-jwt#126
tldr;
- cannot use different domains.
- long-term, we'll be using the same APIs with our mobile app.
- update to check and compare/use the AUD
Concerns
- XSS
config/initializers/cors.rb
models/concerns/users/allowlist.rb
controllers/sessions_controller.rb
routes.rb
controllers/api/v1/users_controller.rb
en.yml
models/user.rb
spec/requests/users_request_spec.rb
Adding usernames:
migration > rails g migration addNamesThemesToUser
config/initializers/devise.rb
controllers/application_controller.rb
model/user.rb
views/devise/mailer/confirmation_instructions.html.erb
Fixing specs:
spec/support/object_creators.rb
Changes:
config/environments/development.rb (port)
Upcoming:
config/routes.rb
controllers/api/v1/users_controller.rb
spec/requests/users_request_spec.rb
In heroku, add Mailgun addon.
Under environment variables, add each:
MAILGUN_SMTP_LOGIN -> MAIL_PROVIDER_USERNAME
MAILGUN_SMTP_PASSWORD -> MAIL_PROVIDER_PASSWORD
MAILGUN_SMTP_PORT -> MAIL_PROVIDER_PORT
MAILGUN_SMTP_SERVER -> MAIL_PROVIDER_ADDRESS
Delete the old MAILGUN_X ones.
In Mailgun, add your email to authorized emails + confirm it.
Promote to production
heroku run rails db:migrate -a ptil-rails-api heroku run rails c -a ptil-rails-api
Addons:
- Bug Tracking - Honeybadger
- Performance / Monitoring - Librato
- Performance / Monitoring - New Relic
- Logging - Logentries
- Redis - Redis Enterprise Cloud
- Cron - Heroku Scheduler
Create Heroku account Install setup Heroku CLI Create pipeline Create staging application Create production application
Install add-ons:
- Heroku Postgres
ENV variables:
- DEVISE_JWT_SECRET_KEY
- RACK_ENV
- RAILS_ENV
- RAILS_LOG_TO_STDOUT
- RAILS_SERVE_STATIC_FILES
- SECRET_KEY_BASE
Procfile
heroku git:remote -a ptil-rails-staging-api git push heroku ep9:master
heroku run rails db:migrate -a ptil-rails-staging-api heroku run rails c -a ptil-rails-staging-api
config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
Gemfile
# Local Emails
# https://github.com/ryanb/letter_opener
gem 'letter_opener'
config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 5000 }
config.action_mailer.delivery_method = :letter_opener
config.action_mailer.raise_delivery_errors = false
config.action_mailer.perform_caching = false
config.action_mailer.perform_deliveries = true
Github Actions
database.yml
test:
<<: *default
database: programmingtil_rails_1_test
password: <%= ENV['POSTGRES_PASSWORD'] %>
username: <%= ENV['POSTGRES_USER'] %>
host: 127.0.0.1
.github/workflows/run_specs.yml
env:
RUBY_VERSION: 3.0.0
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: programmingtil_rails_1_test
DEVISE_JWT_SECRET_KEY: ${{ secrets.DEVISE_JWT_SECRET_KEY }}
name: Rails Specs
on: [push,pull_request]
jobs:
rspec-test:
name: RSpec
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:latest
ports:
- 5432:5432
env:
POSTGRES_USER: ${{ env.POSTGRES_USER }}
POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }}
steps:
- uses: actions/checkout@v1
- uses: actions/setup-ruby@v1
with:
ruby-version: ${{ env.RUBY_VERSION }}
- name: Install postgres client
run: sudo apt-get install libpq-dev
- name: Install dependencies
run: |
gem install bundler
bundler install
- name: Create database
run: |
bundler exec rails db:create RAILS_ENV=test
bundler exec rails db:migrate RAILS_ENV=test
- name: Run tests
run: bundler exec rspec spec/*
- name: Upload coverage results
uses: actions/upload-artifact@master
if: always()
with:
name: coverage-report
path: coverage
gem 'simplecov', require: false
Gemfile
# RSpec for testing
# https://github.com/rspec/rspec-rails
gem 'rspec-rails'
# Needed for rspec
gem 'rexml'
gem 'spring-commands-rspec'
# https://github.com/grosser/parallel_tests
gem 'parallel_tests'
# .env environment variable
# https://github.com/bkeepers/dotenv
gem 'dotenv-rails'
group :test do
# Adds support for Capybara system testing and selenium driver
gem 'capybara', '>= 2.15'
gem 'selenium-webdriver'
# Easy installation and use of web drivers to run system tests with browsers
gem 'webdrivers'
# Code coverage
# https://github.com/colszowka/simplecov
gem 'simplecov', require: false
# Clear out database between runs
# https://github.com/DatabaseCleaner/database_cleaner
gem 'database_cleaner-active_record'
end
routes.rb
# Ping to ensure site is up
resources :ping, only: [:index] do
collection do
get :auth
end
end
en.yml
en:
controllers:
confirmations:
resent: "Confirmation email sent successfully."
success: "Email confirmed successfully."
passwords:
email_required: "Email is required."
email_sent: "Email sent. Please check your inbox."
success: "Password updated successfully. You may need to sign in again."
registrations:
confirm: "Please confirm your email address."
sessions:
sign_out: "Signed out successfully."
application_controllerb.rb confirmations_controller.rb passwords_controller.rb registrations_controller.rb sessions_controller.rb
ping_controller.rb
class PingController < ApplicationController
before_action :authenticate_user!, only: [:auth]
# GET /ping
def index
render body: nil, status: 200
end
# GET /ping/auth
def auth
render body: nil, status: 200
end
end
ping_request_spec.rb
require 'rails_helper'
RSpec.describe "Pings", type: :request do
it 'Returns a status of 200' do
get '/ping/'
expect(response).to have_http_status(200)
end
it 'Returns a status of 401 if not logged in' do
get '/ping/auth/'
expect(response).to have_http_status(401)
end
it 'Returns a status of 200 if logged in' do
user = create_user
headers = get_headers(user.username)
get '/ping/auth/', headers: headers
expect(response).to have_http_status(200)
end
end
devise.rb
config.warden do |manager|
manager.failure_app = DeviseCustomFailure
end
application.rb lib/DeviseCustomFailure.rb models/user.rb
Gemfile
# Annotate models and more
# https://github.com/ctran/annotate_models
gem 'annotate'
bundle install
auto_annotate_models.rake
# NOTE: only doing this in development as some production environments (Heroku)
# NOTE: are sensitive to local FS writes, and besides -- it's just not proper
# NOTE: to have a dev-mode tool do its thing in production.
if Rails.env.development?
require 'annotate'
task :set_annotation_options do
# You can override any of these by setting an environment variable of the
# same name.
Annotate.set_defaults(
'active_admin' => 'false',
'additional_file_patterns' => [],
'routes' => 'false',
'models' => 'true',
'position_in_routes' => 'before',
'position_in_class' => 'before',
'position_in_test' => 'before',
'position_in_fixture' => 'before',
'position_in_factory' => 'before',
'position_in_serializer' => 'before',
'show_foreign_keys' => 'true',
'show_complete_foreign_keys' => 'false',
'show_indexes' => 'true',
'simple_indexes' => 'false',
'model_dir' => 'app/models',
'root_dir' => '',
'include_version' => 'false',
'require' => '',
'exclude_tests' => 'false',
'exclude_fixtures' => 'true',
'exclude_factories' => 'true',
'exclude_serializers' => 'true',
'exclude_scaffolds' => 'true',
'exclude_controllers' => 'true',
'exclude_helpers' => 'true',
'exclude_sti_subclasses' => 'false',
'ignore_model_sub_dir' => 'false',
'ignore_columns' => nil,
'ignore_routes' => nil,
'ignore_unknown_models' => 'false',
'hide_limit_column_types' => 'integer,bigint,boolean',
'hide_default_column_types' => 'json,jsonb,hstore',
'skip_on_db_migrate' => 'false',
'format_bare' => 'true',
'format_rdoc' => 'false',
'format_yard' => 'false',
'format_markdown' => 'false',
'sort' => 'false',
'force' => 'false',
'frozen' => 'false',
'classified_sort' => 'false',
'trace' => 'false',
'wrapper_open' => nil,
'wrapper_close' => nil,
'with_comment' => 'true'
)
end
Annotate.load_tasks
end
rake db:migrate
devise.rb
config.jwt do |jwt|
jwt.secret = ENV['DEVISE_JWT_SECRET_KEY']
jwt.dispatch_requests = [
['POST', %r{^/signin$}],
]
jwt.revocation_requests = [
['DELETE', %r{^/signout$}]
]
jwt.expiration_time = 14.days.to_i
# Use default aud_header
jwt.aud_header = 'JWT_AUD'
end
user.rb
devise :database_authenticatable,
:confirmable,
:registerable,
:recoverable,
:rememberable,
:validatable,
:jwt_authenticatable,
jwt_revocation_strategy: self
rails g migration createAllowlistedJwts
create_allowlisted_jwts.rb
def change
create_table :allowlisted_jwts do |t|
t.references :users, foreign_key: { on_delete: :cascade }, null: false
t.string :jti, null: false
t.string :aud, null: false
t.datetime :exp, null: false
t.string :remote_ip
t.string :os_data
t.string :browser_data
t.string :device_data
t.timestamps null: false
end
add_index :allowlisted_jwts, :jti, unique: true
end
models/allowlisted_jwt.rb
class AllowlistedJwt < ApplicationRecord
end
rake db:migrate
database.yml > host: 127.0.0.1 -> (needed because WSL)
Gemfile
gem 'devise', github: 'heartcombo/devise'
gem 'devise-jwt'
gem 'rack-cors'
rake db:create
rails generate devise:install
rails generate devise user
rails db:migrate
rails new programmingtil-rails-1 --api --database=postgresql