Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ spec/dummy/public/uploads/*

/vendor
docker-compose.override.yml
.env
.env
mysql
node_modules
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,6 @@ gem "puma", "~> 5.0"

gem "fog-aws"
gem 'ddtrace', require: 'ddtrace/auto_instrument'

# For emailing
# gem 'aws-sdk-rails', '~> 3.1.0'
Comment thread
jasonfine marked this conversation as resolved.
Outdated
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ PATH
jquery-ui-rails (~> 6.0.1)
judge (~> 3.1.0)
judge-simple_form (~> 1.1.0)
jwt (~> 2.8.0)
kaminari
mini_magick
rails (>= 5.0)
Expand Down Expand Up @@ -115,6 +116,7 @@ GEM
bundler
rake
thor (>= 0.14.0)
base64 (0.2.0)
bcrypt (3.1.16)
better_errors (2.9.1)
coderay (>= 1.0.0)
Expand Down Expand Up @@ -240,6 +242,8 @@ GEM
judge-simple_form (1.1.0)
judge (>= 2.0)
simple_form (>= 3.0)
jwt (2.8.0)
base64
kaminari (1.2.2)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2)
Expand Down
4 changes: 4 additions & 0 deletions app/assets/javascripts/fae/form/_validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,10 @@ Fae.form.validator = {
);
_this.addCustomValidation();
}
// See https://github.com/wearefine/fae/issues/409
// This was preventing the functionality of a checkbox field
// on the user form.
_this.$password_field.data('validate', '');
},

/**
Expand Down
1 change: 0 additions & 1 deletion app/controllers/fae/deploy_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,5 @@ def deploy_site
end
render json: {success: false}
end

end
end
28 changes: 28 additions & 0 deletions app/controllers/fae/netlify_hooks_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Fae
class NetlifyHooksController < ActionController::Base

def netlify_hook
unless Rails.env.test?
signature = request.headers["X-Webhook-Signature"]
if signature.blank?
Rails.logger.info 'request.headers["X-Webhook-Signature"] header is missing'
return head :forbidden
end
if Fae.netlify[:notification_hook_signature].blank?
Rails.logger.info "Fae.netlify[:notification_hook_signature] is not set"
return head :forbidden
end

options = {iss: "netlify", verify_iss: true, algorithm: "HS256"}
decoded = JWT.decode(signature, Fae.netlify[:notification_hook_signature], true, options)
unless decoded.first['sha256'] == Digest::SHA256.hexdigest(request.body.read)
Rails.logger.info "Netlify hook signature mismatch, check the value of Fae.netlify[:notification_hook_signature] against the value of the JWS secret token in the Netlify webhook settings."
return head :forbidden
end
end

DeployNotifications.notify_admins(request.body.read).deliver_now
return head :ok
end
end
end
29 changes: 29 additions & 0 deletions app/mailers/fae/deploy_notifications.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module Fae
class DeployNotifications < ActionMailer::Base
default from: Fae.deploy_notifications_mailer_sender
layout 'layouts/fae/mailer'

def notify_admins(body = nil, additional_emails = [])
if body.blank?
Rails.logger.info "DeployNotifications.notify_admins called without a body"
return
end
body = JSON.parse(body)

# Don't notify if the deploy is a code push
if body['commit_ref'].present?
Rails.logger.info "#{body['context']} - #{body['id']} is a code push, skipping notification"
Rails.logger.info "#{body['branch']} #{body['commit_ref']}: #{body['commit_message']}"
return
end

@deploy = body
recipients = Fae::User.where(receive_deploy_notifications: true).pluck(:email)
recipients += additional_emails
@fae_options = Fae::Option.instance
current_time_in_zone = Time.now.in_time_zone(@fae_options.time_zone).strftime('%Y-%m-%d %l:%M %p')
subject = "#{@fae_options.title} Deploy Notification #{current_time_in_zone}"
mail(to: recipients, subject: subject)
end
end
end
5 changes: 5 additions & 0 deletions app/models/fae/deploy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Fae
class Deploy < ApplicationRecord
belongs_to :user
end
end
2 changes: 1 addition & 1 deletion app/services/fae/netlify_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def get_deploys
def run_deploy(deploy_hook_type, current_user)
hook = Fae::DeployHook.find_by_environment(deploy_hook_type)
if hook.present?
post("#{hook.url}?trigger_title=#{current_user.full_name.gsub(' ', '+')}+triggered+a+#{deploy_hook_type.titleize}+deploy")
post("#{hook.url}?trigger_title=#{current_user.full_name.gsub(' ', '+')}+triggered+a+#{deploy_hook_type.titleize}+deploy.")
return true
end
false
Expand Down
9 changes: 9 additions & 0 deletions app/views/fae/deploy_notifications/notify_admins.text.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<% if @deploy['title'].present? %>
<%= @deploy['title'].gsub('+', ' ') %>
<% end %>

<% if @deploy['state'] == 'ready' %>
The deploy was successful.
<% else %>
An error occurred. Please contact your FINE team.
Comment thread
jasonfine marked this conversation as resolved.
Outdated
<% end %>
1 change: 1 addition & 0 deletions app/views/fae/users/_form.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
= fae_input f, :first_name
= fae_input f, :last_name
= fae_input f, :email
= fae_input f, :receive_deploy_notifications, helper_text: t('fae.user.receive_deploy_notifications_hint')
= fae_input f, :password, helper_text: t('fae.user.password_hint')
= fae_input f, :password_confirmation
- if current_user.admin? || current_user.super_admin?
Expand Down
1 change: 1 addition & 0 deletions app/views/layouts/fae/mailer.text.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
= yield
1 change: 1 addition & 0 deletions config/locales/fae.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ en:
edit: 'Edit'
user:
password_hint: 'To update your password, fill out the fields below. Otherwise leave blank. Passwords must contain at least 8 characters.'
receive_deploy_notifications_hint: 'Check to receive deploy notifications via email.'
header: 'Your Settings'
last_login: 'Last Logged In'
active: 'Active'
Expand Down
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@

get 'deploy/deploys_list' => 'deploy#deploys_list', as: 'deploy_deploys_list'
post 'deploy/deploy_site' => 'deploy#deploy_site', as: 'deploy_deploy_site'

post 'netlify_hooks/netlify_hook' => 'netlify_hooks#netlify_hook', as: 'netlify_hook'

## catch all 404
match "*path" => 'pages#error404', via: [:get, :post]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AddReceiveDeployNotificationsToFaeUsers < ActiveRecord::Migration[7.0]
def change
add_column :fae_users, :receive_deploy_notifications, :boolean, default: false
add_index :fae_users, :receive_deploy_notifications
end
end
13 changes: 13 additions & 0 deletions db/migrate/20240222165620_create_fae_deploys.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class CreateFaeDeploys < ActiveRecord::Migration[7.0]
def change
create_table :fae_deploys do |t|
t.integer :user_id, index: true
t.string :environment
t.string :deploy_id, index: true
t.string :deploy_status
t.boolean :notified

t.timestamps
end
end
end
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
environment:
- RAILS_ENV=development
- DEV_MYSQL_HOST=db
- FINE_NETLIFY_API_TOKEN=test
- FINE_NETLIFY_API_USER=test
- FINE_NETLIFY_API_TOKEN=abc123
- FINE_NETLIFY_API_USER=user@email.com
db:
# image: postgres
# volumes:
Expand All @@ -37,4 +37,4 @@
ports:
- '3307:3306'
volumes:
- /private/app/dbs:/var/lib/mysql
- ./mysql:/var/lib/mysql
23 changes: 20 additions & 3 deletions docs/features/netlify.md

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doc is a good foundations but I'm going to take a final pass at this before merging.

Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Netlify Deploy Monitor

* [Enabling Deploys](#enabling-deploys)
* [Upgrading](#upgrading)
* [Email Notifications](#email-notifications)

Fae provides and easy integration for statically generated frontends hosted on Netlify. If Fae is delivering content to a statically generated frontend via JSON or GraphQL APIs, updating content in the admin won't trigger a build of the frontend with the new content.
Fae provides an easy integration for statically generated frontends hosted on Netlify. If Fae is delivering content to a statically generated frontend via JSON or GraphQL APIs, updating content in the admin won't trigger a build of the frontend with the new content.

This feature allows a super admin to define multiple deploy environments connected to Netlify deploy hooks. It also establishes a global deploy page so admin and super admin users can trigger builds of any defined Netlify environments.

Expand All @@ -13,8 +13,12 @@ The deploy page will also display the status of the current deploy, along with a

## Enabling Deploys

In your app, run `rake fae:install:migrations` and then migrate the database.

To enable this feature, make sure `config.netlify` is defined in your Fae initializer with all options set correctly.

`notification_hook_signature` is only required if you will use incoming Netlify deploy notification webhooks.

`config/initializers/fae.rb`
```ruby
Fae.setup do |config|
Expand All @@ -26,7 +30,8 @@ Fae.setup do |config|
api_token: 'netlify-api-token',
site: 'site-name-in-netlify',
site_id: 'site-id-in-netlify',
api_base: 'https://api.netlify.com/api/v1/'
api_base: 'https://api.netlify.com/api/v1/',
notification_hook_signature: 'netlify-notification-hook-signature'
}

end
Expand All @@ -35,3 +40,15 @@ end
Then go to the root settings at `/admin/root` while logged in as a super admin and add deploy hooks for each environment you wish to enable in the CMS.

You will see a new nav item labeled "Deploy" that links to `/admin/deploy`. Here you'll be able to trigger and view past deploys.

---

## Email Notifications

In your app, run `rake fae:install:migrations` and then migrate the database.

You can opt-in to email-based notifications for when Netlify deploys complete successfully or fail.

The migration adds a new field to the `Fae::User` model and accompanying checkbox in the user form - `receive_deploy_notifications`, pretty self-explanatory. Any users opted in to this setting will receive the email notifications.

In the https://app.netlify.com dashboard for your project, you'll need to add two new webhook notifications - one for `Deploy succeeded` and `Deploy failed`. Both of these webhooks will point to the same URL: https://your-fae-domain.com/admin/netlify_hooks/netlify_hook (replace your-fae-domain with a real value). Your `JWS secret token` can be any string, but it needs to also be set to, and match what you set it to in the `fae.rb` file's `config.netlify['notification_hook_signature']`.
1 change: 1 addition & 0 deletions docs/topics/initializer.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Fae's default config can be overwritten in a `config/initializers/fae.rb` file.
| --- | ---- | ------- | ----------- |
| devise_secret_key | string | | unique Devise hash, generated on `rails g fae:install` |
| devise_mailer_sender | string | "change-me@example.com" | This email address will get passed to Devise and used as the from address in the password reset emails. |
| deploy_notifications_mailer_sender | string | "change-me@example.com" | This email address will get passed to Fae and used as the from address in the deploy notification emails. |
| dashboard_exclusions | array | [] | The dashboard will show all objects with recent activity. |
| max_image_upload_size | integer | 2 | This will set a file size limit on image uploads in MBs. |
| max_file_upload_size | integer | 5 | This will set a file size limit on file uploads in MBs. |
Expand Down
1 change: 1 addition & 0 deletions fae.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Gem::Specification.new do |s|
s.add_dependency 'remotipart', '~> 1.4.0'
s.add_dependency 'simple_form', '<= 5.1'
s.add_dependency 'slim'
s.add_dependency 'jwt', '~> 2.8.0'

s.add_development_dependency 'appraisal'
s.add_development_dependency 'better_errors'
Expand Down
3 changes: 2 additions & 1 deletion lib/fae/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ class Engine < ::Rails::Engine
require 'slim'
require 'kaminari'
require 'fae/version'
require "sprockets/railtie"
require 'sprockets/railtie'
require 'jwt'

config.eager_load_paths += %W(#{config.root}/app)

Expand Down
35 changes: 18 additions & 17 deletions lib/fae/options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,25 @@

module Fae
# configurable defaults
mattr_accessor :devise_secret_key, :devise_mailer_sender, :dashboard_exclusions, :max_image_upload_size, :max_file_upload_size, :languages, :recreate_versions, :validation_helpers, :track_changes, :tracker_history_length, :slug_separator, :disabled_environments, :per_page, :use_cache, :use_form_manager, :netlify
mattr_accessor :devise_secret_key, :devise_mailer_sender, :deploy_notifications_mailer_sender, :dashboard_exclusions, :max_image_upload_size, :max_file_upload_size, :languages, :recreate_versions, :validation_helpers, :track_changes, :tracker_history_length, :slug_separator, :disabled_environments, :per_page, :use_cache, :use_form_manager, :netlify

self.devise_secret_key = ''
self.devise_mailer_sender = 'change-me@example.com'
self.dashboard_exclusions = []
self.max_image_upload_size = 2
self.max_file_upload_size = 5
self.languages = {}
self.recreate_versions = false
self.validation_helpers = ValidationHelperCollection.new
self.track_changes = true
self.tracker_history_length = 15
self.slug_separator = '-'
self.disabled_environments = []
self.per_page = 25
self.use_cache = false
self.use_form_manager = false
self.netlify = {}
self.devise_secret_key = ''
self.devise_mailer_sender = 'change-me@example.com'
self.deploy_notifications_mailer_sender = 'change-me@example.com'
self.dashboard_exclusions = []
self.max_image_upload_size = 2
self.max_file_upload_size = 5
self.languages = {}
self.recreate_versions = false
self.validation_helpers = ValidationHelperCollection.new
self.track_changes = true
self.tracker_history_length = 15
self.slug_separator = '-'
self.disabled_environments = []
self.per_page = 25
self.use_cache = false
self.use_form_manager = false
self.netlify = {}

# this function maps the vars from your app into your engine
def self.setup(&block)
Expand Down
8 changes: 7 additions & 1 deletion lib/generators/fae/templates/initializers/fae.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
Fae.setup do |config|

## deploy_notifications_mailer_sender
# This email address will get passed to Fae and
# used as the from address in the deploy notification emails.
# config.deploy_notifications_mailer_sender = 'change-me@example.com'

## devise_mailer_sender
# This email address will get passed to Devise and
# used as the from address in the password reset emails.
Expand Down Expand Up @@ -73,6 +78,7 @@
# api_token: 'netlify-api-token',
# site: 'site-name-in-netlify',
# site_id: 'site-id-in-netlify',
# api_base: 'https://api.netlify.com/api/v1/'
# api_base: 'https://api.netlify.com/api/v1/',
# notification_hook_signature: 'netlify-notification-hook-signature'
# }
end
5 changes: 5 additions & 0 deletions lib/generators/fae/templates/initializers/fae_fine.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
Fae.setup do |config|

## deploy_notifications_mailer_sender
# This email address will get passed to Fae and
# used as the from address in the deploy notification emails.
# config.deploy_notifications_mailer_sender = 'change-me@example.com'

## devise_mailer_sender
# This email address will get passed to Devise and
# used as the from address in the password reset emails.
Expand Down
1 change: 1 addition & 0 deletions spec/dummy/app/assets/javascripts/fae.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//= require fae/vendor/trumbowyg

$(document).ready(function(){

$('.login-body').addClass('test-class');

$("body").on("modal:show", function (e) {
Expand Down
Empty file removed spec/dummy/app/mailers/.keep
Empty file.
2 changes: 2 additions & 0 deletions spec/dummy/config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@
# config.action_view.raise_on_missing_translations = true

config.action_mailer.default_url_options = { :host => 'localhost' }

config.hosts << "21b5-47-26-150-197.ngrok-free.app"
end
Loading