Browse Source

Merge branch 'release/0.1.0'

* release/0.1.0:
  Ignore log dir; used and created by capistrano
  Add config for production deploy
  Capify
  Add a subject when sending out.
  Always add a newline after the last message.
  Ensure we don't accidentally commit secrets
  Allow running in dev, using local env
  Allow POSTing a message, mailing to webmaster
  Introduce a test helper to assert a response code
  Configure Pony to run properly in test and production
  Add minitest-assert_changes
  Ignore rubocop blocklength for tests
  Add basic testable and configurable application
  Add boilerplate Makefile
  Add basic sinatra gems needed for testing, mailing and development.
tags/v0.1.0^0
Bèr Kessels 1 year ago
parent
commit
2d4e504499

+ 4
- 0
.gitignore View File

@@ -0,0 +1,4 @@
# Ignore .env, because that may contain secrets.
.env

log/*

+ 3
- 0
.rubocop.yml View File

@@ -0,0 +1,3 @@
Metrics/BlockLength:
Exclude:
- test/**/*_test.rb # We define specs in blocks, which by nature get large.

+ 38
- 0
Capfile View File

@@ -0,0 +1,38 @@
# Load DSL and set up stages
require "capistrano/setup"

# Include default deployment tasks
require "capistrano/deploy"

# Load the SCM plugin appropriate to your project:
#
# require "capistrano/scm/hg"
# install_plugin Capistrano::SCM::Hg
# or
# require "capistrano/scm/svn"
# install_plugin Capistrano::SCM::Svn
# or
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git

# Include tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
# https://github.com/capistrano/rvm
# https://github.com/capistrano/rbenv
# https://github.com/capistrano/chruby
# https://github.com/capistrano/bundler
# https://github.com/capistrano/rails
# https://github.com/capistrano/passenger
#
# require "capistrano/rvm"
# require "capistrano/rbenv"
# require "capistrano/chruby"
# require "capistrano/bundler"
# require "capistrano/rails/assets"
# require "capistrano/rails/migrations"
# require "capistrano/passenger"

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }

+ 26
- 0
Gemfile View File

@@ -0,0 +1,26 @@
# frozen_string_literal: true

source 'https://rubygems.org'

git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

gem 'pony'
gem 'sinatra'

group :development do
gem 'capistrano'
end

group :development, :test do
gem 'minitest'
gem 'minitest-assert_changes'
gem 'rack-test'

gem 'rubocop'

gem 'awesome_print'
gem 'byebug'

gem 'foreman'
gem 'rerun'
end

+ 90
- 0
Gemfile.lock View File

@@ -0,0 +1,90 @@
GEM
remote: https://rubygems.org/
specs:
airbrussh (1.3.0)
sshkit (>= 1.6.1, != 1.7.0)
ast (2.4.0)
awesome_print (1.8.0)
byebug (10.0.2)
capistrano (3.10.2)
airbrussh (>= 1.0.0)
i18n
rake (>= 10.0.0)
sshkit (>= 1.9.0)
concurrent-ruby (1.0.5)
ffi (1.9.23)
foreman (0.84.0)
thor (~> 0.19.1)
i18n (1.0.1)
concurrent-ruby (~> 1.0)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
mail (2.7.0)
mini_mime (>= 0.1.1)
mini_mime (1.0.0)
minitest (5.11.3)
minitest-assert_changes (1.0.0)
minitest
mustermann (1.0.2)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-ssh (4.2.0)
parallel (1.12.1)
parser (2.5.1.0)
ast (~> 2.4.0)
pony (1.12)
mail (>= 2.0)
powerpack (0.1.1)
rack (2.0.5)
rack-protection (2.0.1)
rack
rack-test (1.0.0)
rack (>= 1.0, < 3)
rainbow (3.0.0)
rake (12.3.1)
rb-fsevent (0.10.3)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
rerun (0.13.0)
listen (~> 3.0)
rubocop (0.55.0)
parallel (~> 1.10)
parser (>= 2.5)
powerpack (~> 0.1)
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
ruby-progressbar (1.9.0)
ruby_dep (1.5.0)
sinatra (2.0.1)
mustermann (~> 1.0)
rack (~> 2.0)
rack-protection (= 2.0.1)
tilt (~> 2.0)
sshkit (1.16.0)
net-scp (>= 1.1.2)
net-ssh (>= 2.8.0)
thor (0.19.4)
tilt (2.0.8)
unicode-display_width (1.3.2)

PLATFORMS
ruby

DEPENDENCIES
awesome_print
byebug
capistrano
foreman
minitest
minitest-assert_changes
pony
rack-test
rerun
rubocop
sinatra

BUNDLED WITH
1.16.1

+ 34
- 0
Makefile View File

@@ -0,0 +1,34 @@
CMD_PREFIX=bundle exec

# You want latexmk to *always* run, because make does not have all the info.
# Also, include non-file targets in .PHONY so they are run regardless of any
# file of the given name existing.
.PHONY: all test lint clean setup ruby run packages preprocess

# The first rule in a Makefile is the one executed by default ("make"). It
# should always be the "all" rule, so that "make" and "make all" are identical.
all: test lint

# CUSTOM BUILD RULES
test:
$(CMD_PREFIX) ruby -I lib:test:. -e "Dir.glob('**/*_test.rb') { |f| require(f) }"
lint:
$(CMD_PREFIX) rubocop

clean:
$(CMD_PREFIX) rake db:drop
$(CMD_PREFIX) rake db:create
$(CMD_PREFIX) rake db:migrate

run:
$(CMD_PREFIX) foreman start

##
# Set up the project for building
setup: ruby packages

ruby:
bundle install

packages:
sudo apt install ruby

+ 1
- 0
Procfile View File

@@ -0,0 +1 @@
web: bundle exec rackup

+ 5
- 0
config.ru View File

@@ -0,0 +1,5 @@
$LOAD_PATH << '.'

require 'lib/contact.rb'

run Sinatra::Application

+ 39
- 0
config/deploy.rb View File

@@ -0,0 +1,39 @@
# config valid for current version and patch releases of Capistrano
lock "~> 3.10.2"

set :application, "my_app_name"
set :repo_url, "git@github.com:placebazaar/contact.git"

# Default branch is :master
# ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp

# Default deploy_to directory is /var/www/my_app_name
set :deploy_to, "/u/apps/placebazaar_contact"

# Default value for :format is :airbrussh.
# set :format, :airbrussh

# You can configure the Airbrussh format using :format_options.
# These are the defaults.
# set :format_options, command_output: true, log_file: "log/capistrano.log", color: :auto, truncate: :auto

# Default value for :pty is false
set :pty, true

# Default value for :linked_files is []
# append :linked_files, "config/database.yml"

# Default value for linked_dirs is []
# append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"

# Default value for default_env is {}
# set :default_env, { path: "/opt/ruby/bin:$PATH" }

# Default value for local_user is ENV['USER']
# set :local_user, -> { `git config user.name`.chomp }

# Default value for keep_releases is 5
# set :keep_releases, 5

# Uncomment the following to require manually verifying the host key before first deploy.
# set :ssh_options, verify_host_key: :secure

+ 1
- 0
config/deploy/production.rb View File

@@ -0,0 +1 @@
server "contact.placebazaar.org", user: "deploy", roles: %w{app db web}

+ 61
- 0
config/deploy/staging.rb View File

@@ -0,0 +1,61 @@
# server-based syntax
# ======================
# Defines a single server with a list of roles and multiple properties.
# You can define all roles on a single server, or split them:

# server "example.com", user: "deploy", roles: %w{app db web}, my_property: :my_value
# server "example.com", user: "deploy", roles: %w{app web}, other_property: :other_value
# server "db.example.com", user: "deploy", roles: %w{db}



# role-based syntax
# ==================

# Defines a role with one or multiple servers. The primary server in each
# group is considered to be the first unless any hosts have the primary
# property set. Specify the username and a domain or IP for the server.
# Don't use `:all`, it's a meta role.

# role :app, %w{deploy@example.com}, my_property: :my_value
# role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value
# role :db, %w{deploy@example.com}



# Configuration
# =============
# You can set any configuration variable like in config/deploy.rb
# These variables are then only loaded and set in this stage.
# For available Capistrano configuration variables see the documentation page.
# http://capistranorb.com/documentation/getting-started/configuration/
# Feel free to add new variables to customise your setup.



# Custom SSH Options
# ==================
# You may pass any option but keep in mind that net/ssh understands a
# limited set of options, consult the Net::SSH documentation.
# http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start
#
# Global options
# --------------
# set :ssh_options, {
# keys: %w(/home/rlisowski/.ssh/id_rsa),
# forward_agent: false,
# auth_methods: %w(password)
# }
#
# The server-based syntax can be used to override options:
# ------------------------------------
# server "example.com",
# user: "user_name",
# roles: %w{web app},
# ssh_options: {
# user: "user_name", # overrides user setting above
# keys: %w(/home/user_name/.ssh/id_rsa),
# forward_agent: false,
# auth_methods: %w(publickey password)
# # password: "please use keys"
# }

+ 13
- 0
config/environment.rb View File

@@ -0,0 +1,13 @@
Contact.configure do |config|
config.mail_to = ENV['MAIL_TO']
config.mail_from = 'contact@placebazaar.org'
config.smtp_options = {
address: 'natalie.berk.es',
port: '587',
enable_starttls_auto: true,
user_name: ENV['SMTP_USER'],
password: ENV['SMTP_PASSWORD'],
authentication: :plain,
domain: 'placebazaar.org'
}
end

+ 70
- 0
lib/contact.rb View File

@@ -0,0 +1,70 @@
require 'sinatra'
require 'pony'

UnprocessableEntity = Class.new(StandardError)
BadRequest = Class.new(StandardError)

error UnprocessableEntity do |error|
body "Unprocessable Entity: #{error.message}"
status 422
end

error BadRequest do |error|
body "Bad Request: #{error.message}"
status 400
end

post '/messages' do
validate(params)
raise BadRequest, errors.join("\n") if errors.any?

@name = params['name']
@email = params['email']
@message = params['message']

Pony.mail(reply_to: @email,
subject: "[Contactform PlaceBazaar] #{@name}",
body: erb(:mail_text))
status 201
end

def validate(params)
{ email: 255, name: 255, message: 1000 }.each do |field, length|
errors << "#{field} cannot be over #{length} characters" if
params[field.to_s].to_s.length > length
errors << "#{field} cannot be empty" if params[field].to_s.empty?
end
end

def errors
@errors ||= []
end

## Core namespace for the app
module Contact
## Holds the configuration, singleton with a class variable.
class Config
attr_accessor :mail_to, :mail_from, :smtp_options
end

def self.config
@config ||= Config.new
end

def self.configure
yield config
end

def self.environment
ENV.fetch('RACK_ENV', 'development')
end
end

require_relative '../config/environment.rb'

Pony.options = {
to: Contact.config.mail_to,
from: Contact.config.mail_from,
via: :smtp,
via_options: Contact.config.smtp_options
}

+ 6
- 0
lib/views/mail_text.erb View File

@@ -0,0 +1,6 @@
name: <%= @name %>
email: <%= @email %>
------------------------------------------------------------
<%= @message %>

------------------------------------------------------------

+ 63
- 0
test/integration/create_message_test.rb View File

@@ -0,0 +1,63 @@
require 'test_helper'

describe 'add message through REST' do
describe 'POST /messages' do
let(:params) do
{
name: 'Harry Potter',
email: 'harry@hogwards.edu.wizard',
message: 'Wingardium Leviosar'
}
end

it 'delivers the message over SMTP to reciever' do
assert_changes 'Mail::TestMailer.deliveries.length', from: 0, to: 1 do
post '/messages', params
assert_response 201
assert_equal %w[webmaster@example.com], last_mail.to
assert_equal %w[contact@placebazaar.org], last_mail.from
assert_equal %w[harry@hogwards.edu.wizard], last_mail.reply_to
assert_equal '[Contactform PlaceBazaar] Harry Potter', last_mail.subject
assert_includes last_mail.body.to_s, 'Wingardium Leviosar'
assert_includes last_mail.body.to_s, 'name: Harry Potter'
assert_includes last_mail.body.to_s, 'email: harry@hogwards.edu.wizard'

assert_equal '', last_response.body
end
end

it 'validates that all fields are set' do
assert_no_changes 'Mail::TestMailer.deliveries.length' do
post '/messages', {}
assert_response(400)
end
end

it 'validates length of email' do
assert_no_changes 'Mail::TestMailer.deliveries.length' do
post '/messages', params.merge(email: 'x' * 256)
assert_response(400)
end
end

it 'validates length of name' do
assert_no_changes 'Mail::TestMailer.deliveries.length' do
post '/messages', params.merge(name: 'x' * 256)
assert_response(400)
end
end

it 'validates length of message' do
assert_no_changes 'Mail::TestMailer.deliveries.length' do
post '/messages', params.merge(message: 'x' * 3000)
assert_response(400)
end
end

private

def last_mail
Mail::TestMailer.deliveries.last
end
end
end

+ 9
- 0
test/support/file_helpers.rb View File

@@ -0,0 +1,9 @@
##
# Helpers for testing against files
module FileHelpers
def assert_file_contains(file, string)
contents = File.read(file)
message = %(Expected file "#{file}" to contain "#{string}")
assert_includes(contents, string, message)
end
end

+ 20
- 0
test/support/request_helpers.rb View File

@@ -0,0 +1,20 @@
require 'ostruct'
require 'json'
require 'rack/test'

module RequestHelpers
include Rack::Test::Methods

def app
Sinatra::Application
end

def post_json(url, body, headers = {})
defaults = { 'Content-Type' => 'application/json' }
post url, body.to_json, headers.merge(defaults)
end

def assert_response(code)
assert_equal code, last_response.status
end
end

+ 16
- 0
test/support/time_helpers.rb View File

@@ -0,0 +1,16 @@
module TimeHelpers
##
# Time Helper to freeze the time at die_wende for the duration of a block
# stub goes away once the block is done
def at_die_wende(&block)
Time.stub :now, die_wende do
yield block
end
end

##
# Time Helper, returns a Time
def die_wende
Time.new(1989, 11, 9, 18, 57, 0, 0)
end
end

+ 30
- 0
test/test_helper.rb View File

@@ -0,0 +1,30 @@
require 'minitest/autorun'
require 'minitest/assert_changes'
require 'byebug'

# Set up fake ENV vars
ENV['MAIL_TO'] = 'webmaster@example.com'

require 'contact'

require 'awesome_print'
require 'ostruct'

ENV['RACK_ENV'] = 'test'

Sinatra::Application.environment = :test

## Include all support files
Dir[File.join(__dir__, 'support', '**', '*.rb')].each { |file| require file }

module Minitest
class Spec
include FileHelpers
include RequestHelpers
include TimeHelpers

before do
Pony.override_options = { via: :test }
end
end
end

Loading…
Cancel
Save