Feature: Introduce Super Admins (#705)
* Feature: Introduce Super Admins - added new devise model for super user - added administrate gem - sample dashboards for users and accounts Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
This commit is contained in:
parent
8859880e55
commit
c74b5c21d7
37 changed files with 964 additions and 35 deletions
|
@ -73,10 +73,6 @@ RAILS_LOG_TO_STDOUT=true
|
||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
LOG_SIZE=500
|
LOG_SIZE=500
|
||||||
|
|
||||||
# Credentials to access sidekiq dashboard in production
|
|
||||||
SIDEKIQ_AUTH_USERNAME=
|
|
||||||
SIDEKIQ_AUTH_PASSWORD=
|
|
||||||
|
|
||||||
### This environment variables are only required if you are setting up social media channels
|
### This environment variables are only required if you are setting up social media channels
|
||||||
#facebook
|
#facebook
|
||||||
FB_VERIFY_TOKEN=
|
FB_VERIFY_TOKEN=
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -49,6 +49,8 @@ gem 'devise_token_auth'
|
||||||
# authorization
|
# authorization
|
||||||
gem 'jwt'
|
gem 'jwt'
|
||||||
gem 'pundit'
|
gem 'pundit'
|
||||||
|
# super admin
|
||||||
|
gem 'administrate'
|
||||||
|
|
||||||
##--- gems for pubsub service ---##
|
##--- gems for pubsub service ---##
|
||||||
# https://karolgalanciak.com/blog/2019/11/30/from-activerecord-callbacks-to-publish-slash-subscribe-pattern-and-event-driven-design/
|
# https://karolgalanciak.com/blog/2019/11/30/from-activerecord-callbacks-to-publish-slash-subscribe-pattern-and-event-driven-design/
|
||||||
|
|
32
Gemfile.lock
32
Gemfile.lock
|
@ -84,11 +84,24 @@ GEM
|
||||||
activerecord (>= 5.0, < 6.1)
|
activerecord (>= 5.0, < 6.1)
|
||||||
addressable (2.7.0)
|
addressable (2.7.0)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
|
administrate (0.13.0)
|
||||||
|
actionpack (>= 4.2)
|
||||||
|
actionview (>= 4.2)
|
||||||
|
activerecord (>= 4.2)
|
||||||
|
autoprefixer-rails (>= 6.0)
|
||||||
|
datetime_picker_rails (~> 0.0.7)
|
||||||
|
jquery-rails (>= 4.0)
|
||||||
|
kaminari (>= 1.0)
|
||||||
|
momentjs-rails (~> 2.8)
|
||||||
|
sassc-rails (~> 2.1)
|
||||||
|
selectize-rails (~> 0.6)
|
||||||
annotate (3.1.1)
|
annotate (3.1.1)
|
||||||
activerecord (>= 3.2, < 7.0)
|
activerecord (>= 3.2, < 7.0)
|
||||||
rake (>= 10.4, < 14.0)
|
rake (>= 10.4, < 14.0)
|
||||||
ast (2.4.0)
|
ast (2.4.0)
|
||||||
attr_extras (6.2.3)
|
attr_extras (6.2.3)
|
||||||
|
autoprefixer-rails (9.7.6)
|
||||||
|
execjs
|
||||||
aws-eventstream (1.1.0)
|
aws-eventstream (1.1.0)
|
||||||
aws-partitions (1.310.0)
|
aws-partitions (1.310.0)
|
||||||
aws-sdk-core (3.94.1)
|
aws-sdk-core (3.94.1)
|
||||||
|
@ -141,6 +154,8 @@ GEM
|
||||||
concurrent-ruby (1.1.6)
|
concurrent-ruby (1.1.6)
|
||||||
connection_pool (2.2.2)
|
connection_pool (2.2.2)
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
|
datetime_picker_rails (0.0.7)
|
||||||
|
momentjs-rails (>= 2.8.1)
|
||||||
declarative (0.0.10)
|
declarative (0.0.10)
|
||||||
declarative-option (0.1.0)
|
declarative-option (0.1.0)
|
||||||
descendants_tracker (0.0.4)
|
descendants_tracker (0.0.4)
|
||||||
|
@ -235,6 +250,10 @@ GEM
|
||||||
jbuilder (2.10.0)
|
jbuilder (2.10.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
jmespath (1.4.0)
|
jmespath (1.4.0)
|
||||||
|
jquery-rails (4.3.5)
|
||||||
|
rails-dom-testing (>= 1, < 3)
|
||||||
|
railties (>= 4.2.0)
|
||||||
|
thor (>= 0.14, < 2.0)
|
||||||
json (2.3.0)
|
json (2.3.0)
|
||||||
json_pure (2.3.0)
|
json_pure (2.3.0)
|
||||||
jwt (2.2.1)
|
jwt (2.2.1)
|
||||||
|
@ -278,6 +297,8 @@ GEM
|
||||||
mini_mime (1.0.2)
|
mini_mime (1.0.2)
|
||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.4.0)
|
||||||
minitest (5.14.0)
|
minitest (5.14.0)
|
||||||
|
momentjs-rails (2.20.1)
|
||||||
|
railties (>= 3.1)
|
||||||
msgpack (1.3.3)
|
msgpack (1.3.3)
|
||||||
multi_json (1.14.1)
|
multi_json (1.14.1)
|
||||||
multi_xml (0.6.0)
|
multi_xml (0.6.0)
|
||||||
|
@ -406,6 +427,14 @@ GEM
|
||||||
sass-listen (4.0.0)
|
sass-listen (4.0.0)
|
||||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||||
rb-inotify (~> 0.9, >= 0.9.7)
|
rb-inotify (~> 0.9, >= 0.9.7)
|
||||||
|
sassc (2.3.0)
|
||||||
|
ffi (~> 1.9)
|
||||||
|
sassc-rails (2.1.2)
|
||||||
|
railties (>= 4.0.0)
|
||||||
|
sassc (>= 2.0)
|
||||||
|
sprockets (> 3.0)
|
||||||
|
sprockets-rails
|
||||||
|
tilt
|
||||||
scout_apm (2.6.7)
|
scout_apm (2.6.7)
|
||||||
parser
|
parser
|
||||||
scss_lint (0.59.0)
|
scss_lint (0.59.0)
|
||||||
|
@ -413,6 +442,7 @@ GEM
|
||||||
seed_dump (3.3.1)
|
seed_dump (3.3.1)
|
||||||
activerecord (>= 4)
|
activerecord (>= 4)
|
||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
|
selectize-rails (0.12.6)
|
||||||
semantic_range (2.3.0)
|
semantic_range (2.3.0)
|
||||||
sentry-raven (3.0.0)
|
sentry-raven (3.0.0)
|
||||||
faraday (>= 1.0)
|
faraday (>= 1.0)
|
||||||
|
@ -451,6 +481,7 @@ GEM
|
||||||
telephone_number (1.4.6)
|
telephone_number (1.4.6)
|
||||||
thor (0.20.3)
|
thor (0.20.3)
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
|
tilt (2.0.10)
|
||||||
time_diff (0.3.0)
|
time_diff (0.3.0)
|
||||||
activesupport
|
activesupport
|
||||||
i18n
|
i18n
|
||||||
|
@ -505,6 +536,7 @@ PLATFORMS
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
action-cable-testing
|
action-cable-testing
|
||||||
acts-as-taggable-on
|
acts-as-taggable-on
|
||||||
|
administrate
|
||||||
annotate
|
annotate
|
||||||
attr_extras
|
attr_extras
|
||||||
aws-sdk-s3
|
aws-sdk-s3
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
//= link_tree ../images
|
//= link_tree ../images
|
||||||
|
//= link administrate/application.css
|
||||||
|
//= link administrate/application.js
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
class AccountBuilder
|
class AccountBuilder
|
||||||
include CustomExceptions::Account
|
include CustomExceptions::Account
|
||||||
pattr_initialize [:account_name!, :email!]
|
pattr_initialize [:account_name!, :email!, :confirmed!]
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
validate_email
|
validate_email
|
||||||
|
@ -46,6 +46,7 @@ class AccountBuilder
|
||||||
password: password,
|
password: password,
|
||||||
password_confirmation: password,
|
password_confirmation: password,
|
||||||
name: email_to_name(@email))
|
name: email_to_name(@email))
|
||||||
|
@user.confirm if @confirmed
|
||||||
if @user.save!
|
if @user.save!
|
||||||
link_user_to_account(@user, @account)
|
link_user_to_account(@user, @account)
|
||||||
@user
|
@user
|
||||||
|
|
|
@ -16,7 +16,8 @@ class Api::V1::Accounts::AccountsController < Api::BaseController
|
||||||
def create
|
def create
|
||||||
@user = AccountBuilder.new(
|
@user = AccountBuilder.new(
|
||||||
account_name: account_params[:account_name],
|
account_name: account_params[:account_name],
|
||||||
email: account_params[:email]
|
email: account_params[:email],
|
||||||
|
confirmed: confirmed?
|
||||||
).perform
|
).perform
|
||||||
if @user
|
if @user
|
||||||
send_auth_headers(@user)
|
send_auth_headers(@user)
|
||||||
|
@ -40,6 +41,10 @@ class Api::V1::Accounts::AccountsController < Api::BaseController
|
||||||
authorize(Account)
|
authorize(Account)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def confirmed?
|
||||||
|
super_admin? && params[:confirmed]
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_account
|
def fetch_account
|
||||||
@account = current_user.accounts.find(params[:id])
|
@account = current_user.accounts.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,17 +4,25 @@ module AccessTokenAuthHelper
|
||||||
'api/v1/accounts/conversations/messages' => ['create']
|
'api/v1/accounts/conversations/messages' => ['create']
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
def authenticate_access_token!
|
def ensure_access_token
|
||||||
token = request.headers[:api_access_token] || request.headers[:HTTP_API_ACCESS_TOKEN]
|
token = request.headers[:api_access_token] || request.headers[:HTTP_API_ACCESS_TOKEN]
|
||||||
access_token = AccessToken.find_by(token: token)
|
@access_token = AccessToken.find_by(token: token) if token.present?
|
||||||
render_unauthorized('Invalid Access Token') && return unless access_token
|
end
|
||||||
|
|
||||||
token_owner = access_token.owner
|
def authenticate_access_token!
|
||||||
@resource = token_owner
|
ensure_access_token
|
||||||
|
render_unauthorized('Invalid Access Token') && return if @access_token.blank?
|
||||||
|
|
||||||
|
@resource = @access_token.owner
|
||||||
|
end
|
||||||
|
|
||||||
|
def super_admin?
|
||||||
|
@resource.present? && @resource.is_a?(SuperAdmin)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_bot_access_token!
|
def validate_bot_access_token!
|
||||||
return if current_user.is_a?(User)
|
return if current_user.is_a?(User)
|
||||||
|
return if super_admin?
|
||||||
return if agent_bot_accessible?
|
return if agent_bot_accessible?
|
||||||
|
|
||||||
render_unauthorized('Access to this endpoint is not authorized for bots')
|
render_unauthorized('Access to this endpoint is not authorized for bots')
|
||||||
|
|
44
app/controllers/super_admin/access_tokens_controller.rb
Normal file
44
app/controllers/super_admin/access_tokens_controller.rb
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
class SuperAdmin::AccessTokensController < SuperAdmin::ApplicationController
|
||||||
|
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||||
|
# For example, you may want to send an email after a foo is updated.
|
||||||
|
#
|
||||||
|
# def update
|
||||||
|
# super
|
||||||
|
# send_foo_updated_email(requested_resource)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Override this method to specify custom lookup behavior.
|
||||||
|
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||||
|
# actions.
|
||||||
|
#
|
||||||
|
# def find_resource(param)
|
||||||
|
# Foo.find_by!(slug: param)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# The result of this lookup will be available as `requested_resource`
|
||||||
|
|
||||||
|
# Override this if you have certain roles that require a subset
|
||||||
|
# this will be used to set the records shown on the `index` action.
|
||||||
|
#
|
||||||
|
# def scoped_resource
|
||||||
|
# if current_user.super_admin?
|
||||||
|
# resource_class
|
||||||
|
# else
|
||||||
|
# resource_class.with_less_stuff
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Override `resource_params` if you want to transform the submitted
|
||||||
|
# data before it's persisted. For example, the following would turn all
|
||||||
|
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||||
|
# and `dashboard`:
|
||||||
|
#
|
||||||
|
# def resource_params
|
||||||
|
# params.require(resource_class.model_name.param_key).
|
||||||
|
# permit(dashboard.permitted_attributes).
|
||||||
|
# transform_values { |value| value == "" ? nil : value }
|
||||||
|
# end
|
||||||
|
|
||||||
|
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
||||||
|
# for more information
|
||||||
|
end
|
44
app/controllers/super_admin/accounts_controller.rb
Normal file
44
app/controllers/super_admin/accounts_controller.rb
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
class SuperAdmin::AccountsController < SuperAdmin::ApplicationController
|
||||||
|
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||||
|
# For example, you may want to send an email after a foo is updated.
|
||||||
|
#
|
||||||
|
# def update
|
||||||
|
# super
|
||||||
|
# send_foo_updated_email(requested_resource)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Override this method to specify custom lookup behavior.
|
||||||
|
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||||
|
# actions.
|
||||||
|
#
|
||||||
|
# def find_resource(param)
|
||||||
|
# Foo.find_by!(slug: param)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# The result of this lookup will be available as `requested_resource`
|
||||||
|
|
||||||
|
# Override this if you have certain roles that require a subset
|
||||||
|
# this will be used to set the records shown on the `index` action.
|
||||||
|
#
|
||||||
|
# def scoped_resource
|
||||||
|
# if current_user.super_admin?
|
||||||
|
# resource_class
|
||||||
|
# else
|
||||||
|
# resource_class.with_less_stuff
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Override `resource_params` if you want to transform the submitted
|
||||||
|
# data before it's persisted. For example, the following would turn all
|
||||||
|
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||||
|
# and `dashboard`:
|
||||||
|
#
|
||||||
|
# def resource_params
|
||||||
|
# params.require(resource_class.model_name.param_key).
|
||||||
|
# permit(dashboard.permitted_attributes).
|
||||||
|
# transform_values { |value| value == "" ? nil : value }
|
||||||
|
# end
|
||||||
|
|
||||||
|
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
||||||
|
# for more information
|
||||||
|
end
|
16
app/controllers/super_admin/application_controller.rb
Normal file
16
app/controllers/super_admin/application_controller.rb
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# All Administrate controllers inherit from this
|
||||||
|
# `Administrate::ApplicationController`, making it the ideal place to put
|
||||||
|
# authentication logic or other before_actions.
|
||||||
|
#
|
||||||
|
# If you want to add pagination or other controller-level concerns,
|
||||||
|
# you're free to overwrite the RESTful controller actions.
|
||||||
|
class SuperAdmin::ApplicationController < Administrate::ApplicationController
|
||||||
|
# authenticiation done via devise : SuperAdmin Model
|
||||||
|
before_action :authenticate_super_admin!
|
||||||
|
|
||||||
|
# Override this value to specify the number of elements to display at a time
|
||||||
|
# on index pages. Defaults to 20.
|
||||||
|
# def records_per_page
|
||||||
|
# params[:per_page] || 20
|
||||||
|
# end
|
||||||
|
end
|
28
app/controllers/super_admin/devise/sessions_controller.rb
Normal file
28
app/controllers/super_admin/devise/sessions_controller.rb
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SuperAdmin::Devise::SessionsController < Devise::SessionsController
|
||||||
|
def new
|
||||||
|
self.resource = resource_class.new(sign_in_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
return unless valid_credentials?
|
||||||
|
|
||||||
|
sign_in(@super_admin, scope: :super_admin)
|
||||||
|
flash.discard
|
||||||
|
redirect_to super_admin_users_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
sign_out
|
||||||
|
flash.discard
|
||||||
|
redirect_to '/'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def valid_credentials?
|
||||||
|
@super_admin = SuperAdmin.find_by!(email: params[:super_admin][:email])
|
||||||
|
@super_admin.valid_password?(params[:super_admin][:password])
|
||||||
|
end
|
||||||
|
end
|
44
app/controllers/super_admin/super_admins_controller.rb
Normal file
44
app/controllers/super_admin/super_admins_controller.rb
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
class SuperAdmin::SuperAdminsController < SuperAdmin::ApplicationController
|
||||||
|
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||||
|
# For example, you may want to send an email after a foo is updated.
|
||||||
|
#
|
||||||
|
# def update
|
||||||
|
# super
|
||||||
|
# send_foo_updated_email(requested_resource)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Override this method to specify custom lookup behavior.
|
||||||
|
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||||
|
# actions.
|
||||||
|
#
|
||||||
|
# def find_resource(param)
|
||||||
|
# Foo.find_by!(slug: param)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# The result of this lookup will be available as `requested_resource`
|
||||||
|
|
||||||
|
# Override this if you have certain roles that require a subset
|
||||||
|
# this will be used to set the records shown on the `index` action.
|
||||||
|
#
|
||||||
|
# def scoped_resource
|
||||||
|
# if current_user.super_admin?
|
||||||
|
# resource_class
|
||||||
|
# else
|
||||||
|
# resource_class.with_less_stuff
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Override `resource_params` if you want to transform the submitted
|
||||||
|
# data before it's persisted. For example, the following would turn all
|
||||||
|
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||||
|
# and `dashboard`:
|
||||||
|
#
|
||||||
|
# def resource_params
|
||||||
|
# params.require(resource_class.model_name.param_key).
|
||||||
|
# permit(dashboard.permitted_attributes).
|
||||||
|
# transform_values { |value| value == "" ? nil : value }
|
||||||
|
# end
|
||||||
|
|
||||||
|
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
||||||
|
# for more information
|
||||||
|
end
|
44
app/controllers/super_admin/users_controller.rb
Normal file
44
app/controllers/super_admin/users_controller.rb
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
class SuperAdmin::UsersController < SuperAdmin::ApplicationController
|
||||||
|
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||||
|
# For example, you may want to send an email after a foo is updated.
|
||||||
|
#
|
||||||
|
# def update
|
||||||
|
# super
|
||||||
|
# send_foo_updated_email(requested_resource)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Override this method to specify custom lookup behavior.
|
||||||
|
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||||
|
# actions.
|
||||||
|
#
|
||||||
|
# def find_resource(param)
|
||||||
|
# Foo.find_by!(slug: param)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# The result of this lookup will be available as `requested_resource`
|
||||||
|
|
||||||
|
# Override this if you have certain roles that require a subset
|
||||||
|
# this will be used to set the records shown on the `index` action.
|
||||||
|
#
|
||||||
|
# def scoped_resource
|
||||||
|
# if current_user.super_admin?
|
||||||
|
# resource_class
|
||||||
|
# else
|
||||||
|
# resource_class.with_less_stuff
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Override `resource_params` if you want to transform the submitted
|
||||||
|
# data before it's persisted. For example, the following would turn all
|
||||||
|
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||||
|
# and `dashboard`:
|
||||||
|
#
|
||||||
|
# def resource_params
|
||||||
|
# params.require(resource_class.model_name.param_key).
|
||||||
|
# permit(dashboard.permitted_attributes).
|
||||||
|
# transform_values { |value| value == "" ? nil : value }
|
||||||
|
# end
|
||||||
|
|
||||||
|
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
||||||
|
# for more information
|
||||||
|
end
|
66
app/dashboards/access_token_dashboard.rb
Normal file
66
app/dashboards/access_token_dashboard.rb
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
require 'administrate/base_dashboard'
|
||||||
|
|
||||||
|
class AccessTokenDashboard < Administrate::BaseDashboard
|
||||||
|
# ATTRIBUTE_TYPES
|
||||||
|
# a hash that describes the type of each of the model's fields.
|
||||||
|
#
|
||||||
|
# Each different type represents an Administrate::Field object,
|
||||||
|
# which determines how the attribute is displayed
|
||||||
|
# on pages throughout the dashboard.
|
||||||
|
ATTRIBUTE_TYPES = {
|
||||||
|
owner: Field::Polymorphic,
|
||||||
|
id: Field::Number,
|
||||||
|
token: Field::String,
|
||||||
|
created_at: Field::DateTime,
|
||||||
|
updated_at: Field::DateTime
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
# COLLECTION_ATTRIBUTES
|
||||||
|
# an array of attributes that will be displayed on the model's index page.
|
||||||
|
#
|
||||||
|
# By default, it's limited to four items to reduce clutter on index pages.
|
||||||
|
# Feel free to add, remove, or rearrange items.
|
||||||
|
COLLECTION_ATTRIBUTES = %i[
|
||||||
|
owner
|
||||||
|
id
|
||||||
|
token
|
||||||
|
created_at
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# SHOW_PAGE_ATTRIBUTES
|
||||||
|
# an array of attributes that will be displayed on the model's show page.
|
||||||
|
SHOW_PAGE_ATTRIBUTES = %i[
|
||||||
|
owner
|
||||||
|
id
|
||||||
|
token
|
||||||
|
created_at
|
||||||
|
updated_at
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# FORM_ATTRIBUTES
|
||||||
|
# an array of attributes that will be displayed
|
||||||
|
# on the model's form (`new` and `edit`) pages.
|
||||||
|
FORM_ATTRIBUTES = %i[
|
||||||
|
owner
|
||||||
|
token
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# COLLECTION_FILTERS
|
||||||
|
# a hash that defines filters that can be used while searching via the search
|
||||||
|
# field of the dashboard.
|
||||||
|
#
|
||||||
|
# For example to add an option to search for open resources by typing "open:"
|
||||||
|
# in the search field:
|
||||||
|
#
|
||||||
|
# COLLECTION_FILTERS = {
|
||||||
|
# open: ->(resources) { resources.where(open: true) }
|
||||||
|
# }.freeze
|
||||||
|
COLLECTION_FILTERS = {}.freeze
|
||||||
|
|
||||||
|
# Overwrite this method to customize how access tokens are displayed
|
||||||
|
# across all pages of the admin dashboard.
|
||||||
|
#
|
||||||
|
# def display_resource(access_token)
|
||||||
|
# "AccessToken ##{access_token.id}"
|
||||||
|
# end
|
||||||
|
end
|
64
app/dashboards/account_dashboard.rb
Normal file
64
app/dashboards/account_dashboard.rb
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
require 'administrate/base_dashboard'
|
||||||
|
|
||||||
|
class AccountDashboard < Administrate::BaseDashboard
|
||||||
|
# ATTRIBUTE_TYPES
|
||||||
|
# a hash that describes the type of each of the model's fields.
|
||||||
|
#
|
||||||
|
# Each different type represents an Administrate::Field object,
|
||||||
|
# which determines how the attribute is displayed
|
||||||
|
# on pages throughout the dashboard.
|
||||||
|
ATTRIBUTE_TYPES = {
|
||||||
|
id: Field::Number,
|
||||||
|
name: Field::String,
|
||||||
|
created_at: Field::DateTime,
|
||||||
|
updated_at: Field::DateTime,
|
||||||
|
locale: Field::String.with_options(searchable: false)
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
# COLLECTION_ATTRIBUTES
|
||||||
|
# an array of attributes that will be displayed on the model's index page.
|
||||||
|
#
|
||||||
|
# By default, it's limited to four items to reduce clutter on index pages.
|
||||||
|
# Feel free to add, remove, or rearrange items.
|
||||||
|
COLLECTION_ATTRIBUTES = %i[
|
||||||
|
name
|
||||||
|
locale
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# SHOW_PAGE_ATTRIBUTES
|
||||||
|
# an array of attributes that will be displayed on the model's show page.
|
||||||
|
SHOW_PAGE_ATTRIBUTES = %i[
|
||||||
|
id
|
||||||
|
name
|
||||||
|
created_at
|
||||||
|
updated_at
|
||||||
|
locale
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# FORM_ATTRIBUTES
|
||||||
|
# an array of attributes that will be displayed
|
||||||
|
# on the model's form (`new` and `edit`) pages.
|
||||||
|
FORM_ATTRIBUTES = %i[
|
||||||
|
name
|
||||||
|
locale
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# COLLECTION_FILTERS
|
||||||
|
# a hash that defines filters that can be used while searching via the search
|
||||||
|
# field of the dashboard.
|
||||||
|
#
|
||||||
|
# For example to add an option to search for open resources by typing "open:"
|
||||||
|
# in the search field:
|
||||||
|
#
|
||||||
|
# COLLECTION_FILTERS = {
|
||||||
|
# open: ->(resources) { resources.where(open: true) }
|
||||||
|
# }.freeze
|
||||||
|
COLLECTION_FILTERS = {}.freeze
|
||||||
|
|
||||||
|
# Overwrite this method to customize how accounts are displayed
|
||||||
|
# across all pages of the admin dashboard.
|
||||||
|
#
|
||||||
|
# def display_resource(account)
|
||||||
|
# "Account ##{account.id}"
|
||||||
|
# end
|
||||||
|
end
|
81
app/dashboards/super_admin_dashboard.rb
Normal file
81
app/dashboards/super_admin_dashboard.rb
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
require 'administrate/base_dashboard'
|
||||||
|
|
||||||
|
class SuperAdminDashboard < Administrate::BaseDashboard
|
||||||
|
# ATTRIBUTE_TYPES
|
||||||
|
# a hash that describes the type of each of the model's fields.
|
||||||
|
#
|
||||||
|
# Each different type represents an Administrate::Field object,
|
||||||
|
# which determines how the attribute is displayed
|
||||||
|
# on pages throughout the dashboard.
|
||||||
|
ATTRIBUTE_TYPES = {
|
||||||
|
id: Field::Number,
|
||||||
|
email: Field::String,
|
||||||
|
access_token: Field::HasOne,
|
||||||
|
remember_created_at: Field::DateTime,
|
||||||
|
sign_in_count: Field::Number,
|
||||||
|
current_sign_in_at: Field::DateTime,
|
||||||
|
last_sign_in_at: Field::DateTime,
|
||||||
|
current_sign_in_ip: Field::String.with_options(searchable: false),
|
||||||
|
last_sign_in_ip: Field::String.with_options(searchable: false),
|
||||||
|
created_at: Field::DateTime,
|
||||||
|
updated_at: Field::DateTime
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
# COLLECTION_ATTRIBUTES
|
||||||
|
# an array of attributes that will be displayed on the model's index page.
|
||||||
|
#
|
||||||
|
# By default, it's limited to four items to reduce clutter on index pages.
|
||||||
|
# Feel free to add, remove, or rearrange items.
|
||||||
|
COLLECTION_ATTRIBUTES = %i[
|
||||||
|
id
|
||||||
|
email
|
||||||
|
access_token
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# SHOW_PAGE_ATTRIBUTES
|
||||||
|
# an array of attributes that will be displayed on the model's show page.
|
||||||
|
SHOW_PAGE_ATTRIBUTES = %i[
|
||||||
|
id
|
||||||
|
email
|
||||||
|
remember_created_at
|
||||||
|
sign_in_count
|
||||||
|
current_sign_in_at
|
||||||
|
last_sign_in_at
|
||||||
|
current_sign_in_ip
|
||||||
|
last_sign_in_ip
|
||||||
|
created_at
|
||||||
|
updated_at
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# FORM_ATTRIBUTES
|
||||||
|
# an array of attributes that will be displayed
|
||||||
|
# on the model's form (`new` and `edit`) pages.
|
||||||
|
FORM_ATTRIBUTES = %i[
|
||||||
|
email
|
||||||
|
remember_created_at
|
||||||
|
sign_in_count
|
||||||
|
current_sign_in_at
|
||||||
|
last_sign_in_at
|
||||||
|
current_sign_in_ip
|
||||||
|
last_sign_in_ip
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# COLLECTION_FILTERS
|
||||||
|
# a hash that defines filters that can be used while searching via the search
|
||||||
|
# field of the dashboard.
|
||||||
|
#
|
||||||
|
# For example to add an option to search for open resources by typing "open:"
|
||||||
|
# in the search field:
|
||||||
|
#
|
||||||
|
# COLLECTION_FILTERS = {
|
||||||
|
# open: ->(resources) { resources.where(open: true) }
|
||||||
|
# }.freeze
|
||||||
|
COLLECTION_FILTERS = {}.freeze
|
||||||
|
|
||||||
|
# Overwrite this method to customize how super admins are displayed
|
||||||
|
# across all pages of the admin dashboard.
|
||||||
|
#
|
||||||
|
# def display_resource(super_admin)
|
||||||
|
# "SuperAdmin ##{super_admin.id}"
|
||||||
|
# end
|
||||||
|
end
|
88
app/dashboards/user_dashboard.rb
Normal file
88
app/dashboards/user_dashboard.rb
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
require 'administrate/base_dashboard'
|
||||||
|
|
||||||
|
class UserDashboard < Administrate::BaseDashboard
|
||||||
|
# ATTRIBUTE_TYPES
|
||||||
|
# a hash that describes the type of each of the model's fields.
|
||||||
|
#
|
||||||
|
# Each different type represents an Administrate::Field object,
|
||||||
|
# which determines how the attribute is displayed
|
||||||
|
# on pages throughout the dashboard.
|
||||||
|
ATTRIBUTE_TYPES = {
|
||||||
|
account_users: Field::HasMany,
|
||||||
|
accounts: Field::HasMany,
|
||||||
|
invitees: Field::HasMany.with_options(class_name: 'User'),
|
||||||
|
id: Field::Number,
|
||||||
|
provider: Field::String,
|
||||||
|
uid: Field::String,
|
||||||
|
reset_password_token: Field::String,
|
||||||
|
reset_password_sent_at: Field::DateTime,
|
||||||
|
remember_created_at: Field::DateTime,
|
||||||
|
sign_in_count: Field::Number,
|
||||||
|
current_sign_in_at: Field::DateTime,
|
||||||
|
last_sign_in_at: Field::DateTime,
|
||||||
|
current_sign_in_ip: Field::String,
|
||||||
|
last_sign_in_ip: Field::String,
|
||||||
|
confirmation_token: Field::String,
|
||||||
|
confirmed_at: Field::DateTime,
|
||||||
|
confirmation_sent_at: Field::DateTime,
|
||||||
|
unconfirmed_email: Field::String,
|
||||||
|
name: Field::String,
|
||||||
|
nickname: Field::String,
|
||||||
|
email: Field::String,
|
||||||
|
tokens: Field::String.with_options(searchable: false),
|
||||||
|
created_at: Field::DateTime,
|
||||||
|
updated_at: Field::DateTime,
|
||||||
|
pubsub_token: Field::String
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
# COLLECTION_ATTRIBUTES
|
||||||
|
# an array of attributes that will be displayed on the model's index page.
|
||||||
|
#
|
||||||
|
# By default, it's limited to four items to reduce clutter on index pages.
|
||||||
|
# Feel free to add, remove, or rearrange items.
|
||||||
|
COLLECTION_ATTRIBUTES = %i[
|
||||||
|
name
|
||||||
|
email
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# SHOW_PAGE_ATTRIBUTES
|
||||||
|
# an array of attributes that will be displayed on the model's show page.
|
||||||
|
SHOW_PAGE_ATTRIBUTES = %i[
|
||||||
|
accounts
|
||||||
|
id
|
||||||
|
unconfirmed_email
|
||||||
|
name
|
||||||
|
nickname
|
||||||
|
email
|
||||||
|
created_at
|
||||||
|
updated_at
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# FORM_ATTRIBUTES
|
||||||
|
# an array of attributes that will be displayed
|
||||||
|
# on the model's form (`new` and `edit`) pages.
|
||||||
|
FORM_ATTRIBUTES = %i[
|
||||||
|
name
|
||||||
|
nickname
|
||||||
|
email
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# COLLECTION_FILTERS
|
||||||
|
# a hash that defines filters that can be used while searching via the search
|
||||||
|
# field of the dashboard.
|
||||||
|
#
|
||||||
|
# For example to add an option to search for open resources by typing "open:"
|
||||||
|
# in the search field:
|
||||||
|
#
|
||||||
|
# COLLECTION_FILTERS = {
|
||||||
|
# open: ->(resources) { resources.where(open: true) }
|
||||||
|
# }.freeze
|
||||||
|
COLLECTION_FILTERS = {}.freeze
|
||||||
|
|
||||||
|
# Overwrite this method to customize how users are displayed
|
||||||
|
# across all pages of the admin dashboard.
|
||||||
|
#
|
||||||
|
# def display_resource(user)
|
||||||
|
# "User ##{user.id}"
|
||||||
|
# end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
@import '../variables';
|
||||||
|
|
||||||
|
.superadmin-body {
|
||||||
|
background: $color-background;
|
||||||
|
}
|
13
app/javascript/dashboard/assets/scss/super_admin/pages.scss
Normal file
13
app/javascript/dashboard/assets/scss/super_admin/pages.scss
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
@import 'shared/assets/fonts/inter';
|
||||||
|
@import '../variables';
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: $color-background;
|
||||||
|
font-family: Inter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
background-color: $color-woot;
|
||||||
|
border-radius: 1px solid $color-woot;
|
||||||
|
color: $color-white;
|
||||||
|
}
|
2
app/javascript/packs/superadmin.js
Normal file
2
app/javascript/packs/superadmin.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import '../dashboard/assets/scss/app.scss';
|
||||||
|
import '../dashboard/assets/scss/super_admin/index.scss';
|
1
app/javascript/packs/superadmin_pages.js
Normal file
1
app/javascript/packs/superadmin_pages.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
import '../dashboard/assets/scss/super_admin/pages.scss';
|
27
app/models/super_admin.rb
Normal file
27
app/models/super_admin.rb
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: super_admins
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# current_sign_in_at :datetime
|
||||||
|
# current_sign_in_ip :inet
|
||||||
|
# email :string default(""), not null
|
||||||
|
# encrypted_password :string default(""), not null
|
||||||
|
# last_sign_in_at :datetime
|
||||||
|
# last_sign_in_ip :inet
|
||||||
|
# remember_created_at :datetime
|
||||||
|
# sign_in_count :integer default(0), not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_super_admins_on_email (email) UNIQUE
|
||||||
|
#
|
||||||
|
class SuperAdmin < ApplicationRecord
|
||||||
|
# Include default devise modules. Others available are:
|
||||||
|
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
|
||||||
|
devise :database_authenticatable, :trackable, :rememberable, :validatable
|
||||||
|
|
||||||
|
include AccessTokenable
|
||||||
|
end
|
27
app/views/super_admin/application/_navigation.html.erb
Normal file
27
app/views/super_admin/application/_navigation.html.erb
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<%#
|
||||||
|
# Navigation
|
||||||
|
|
||||||
|
This partial is used to display the navigation in Administrate.
|
||||||
|
By default, the navigation contains navigation links
|
||||||
|
for all resources in the admin dashboard,
|
||||||
|
as defined by the routes in the `admin/` namespace
|
||||||
|
%>
|
||||||
|
|
||||||
|
<%= javascript_pack_tag 'superadmin_pages' %>
|
||||||
|
<%= stylesheet_pack_tag 'superadmin_pages' %>
|
||||||
|
|
||||||
|
|
||||||
|
<nav class="navigation" role="navigation">
|
||||||
|
<%= link_to "Back to app", root_url, class: "button button--alt" %>
|
||||||
|
<%= link_to "Logout", super_admin_logout_url , class: "button button--alt" %>
|
||||||
|
|
||||||
|
<% Administrate::Namespace.new(namespace).resources.each do |resource| %>
|
||||||
|
<%= link_to(
|
||||||
|
display_resource_name(resource),
|
||||||
|
[namespace, resource_index_route_key(resource)],
|
||||||
|
class: "navigation__link navigation__link--#{nav_link_state(resource)}"
|
||||||
|
) if valid_action? :index, resource %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= link_to "Sidekiq", sidekiq_web_url , class: "button" %>
|
||||||
|
</nav>
|
43
app/views/super_admin/devise/sessions/new.html.erb
Normal file
43
app/views/super_admin/devise/sessions/new.html.erb
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>SuperAdmin | Chatwoot</title>
|
||||||
|
<%= javascript_pack_tag 'superadmin' %>
|
||||||
|
<%= stylesheet_pack_tag 'superadmin' %>
|
||||||
|
</head>
|
||||||
|
<body data-gr-c-s-loaded="true">
|
||||||
|
<div id="app" class="superadmin-body app-wrapper app-root">
|
||||||
|
<div class="medium column login">
|
||||||
|
<div class="text-center medium-12 login__hero align-self-top">
|
||||||
|
<h2 class="hero__title">
|
||||||
|
Howdy, admin 👋
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="row align-center">
|
||||||
|
<div class="small-12 medium-4 column">
|
||||||
|
<%= form_for(resource, as: resource_name, url: '/super_admin/sign_in', html: { class: 'login-box column align-self-top'}) do |f| %>
|
||||||
|
<div class="column log-in-form">
|
||||||
|
<label>
|
||||||
|
Email
|
||||||
|
<%= f.email_field :email, autofocus: true, autocomplete: "email", placeholder: "Email eg: someone@example.com" %>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Password
|
||||||
|
<%= f.password_field :password, autocomplete: "current-password", placeholder: "Password" %>
|
||||||
|
</label>
|
||||||
|
<p>
|
||||||
|
<%= f.check_box :remember_me %> Remember me
|
||||||
|
</p>
|
||||||
|
<button type="submit" class="button nice large expanded">
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div class="column text-center sigin__footer">
|
||||||
|
© Chatwoot
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -6,7 +6,7 @@ Devise.setup do |config|
|
||||||
# confirmation, reset password and unlock tokens in the database.
|
# confirmation, reset password and unlock tokens in the database.
|
||||||
# Devise will use the `secret_key_base` as its `secret_key`
|
# Devise will use the `secret_key_base` as its `secret_key`
|
||||||
# by default. You can change it below and use your own secret key.
|
# by default. You can change it below and use your own secret key.
|
||||||
# config.secret_key = 'dff4665a082305d28b485d1d763d0d3e52e2577220eaa551836862a3dbca1aade309fe7ceed35180ac494cbc27bd2f5f84d45e4d19530598d1bd899dcbb115e1'
|
# config.secret_key = 'dff4665a082305d28b485d1d763d0d3e52e2577220eaa551836862a3dbca1aade309fe7ceed35180ac494cbc27bd2f5f84d45e1'
|
||||||
|
|
||||||
# ==> Mailer Configuration
|
# ==> Mailer Configuration
|
||||||
# Configure the e-mail address which will be shown in Devise::Mailer,
|
# Configure the e-mail address which will be shown in Devise::Mailer,
|
||||||
|
@ -220,15 +220,15 @@ Devise.setup do |config|
|
||||||
# Turn scoped views on. Before rendering "sessions/new", it will first check for
|
# Turn scoped views on. Before rendering "sessions/new", it will first check for
|
||||||
# "users/sessions/new". It's turned off by default because it's slower if you
|
# "users/sessions/new". It's turned off by default because it's slower if you
|
||||||
# are using only default views.
|
# are using only default views.
|
||||||
# config.scoped_views = false
|
config.scoped_views = true
|
||||||
|
|
||||||
# Configure the default scope given to Warden. By default it's the first
|
# Configure the default scope given to Warden. By default it's the first
|
||||||
# devise role declared in your routes (usually :user).
|
# devise role declared in your routes (usually :user).
|
||||||
# config.default_scope = :user
|
config.default_scope = :user
|
||||||
|
|
||||||
# Set this configuration to false if you want /users/sign_out to sign out
|
# Set this configuration to false if you want /users/sign_out to sign out
|
||||||
# only the current scope. By default, Devise signs out all scopes.
|
# only the current scope. By default, Devise signs out all scopes.
|
||||||
# config.sign_out_all_scopes = true
|
config.sign_out_all_scopes = true
|
||||||
|
|
||||||
# ==> Navigation configuration
|
# ==> Navigation configuration
|
||||||
# Lists the formats that should be treated as navigational. Formats like
|
# Lists the formats that should be treated as navigational. Formats like
|
||||||
|
|
|
@ -168,20 +168,20 @@ Rails.application.routes.draw do
|
||||||
# Internal Monitoring Routes
|
# Internal Monitoring Routes
|
||||||
require 'sidekiq/web'
|
require 'sidekiq/web'
|
||||||
|
|
||||||
scope :monitoring do
|
devise_for :super_admins, path: 'super_admin', controllers: { sessions: 'super_admin/devise/sessions' }
|
||||||
# Sidekiq should use basic auth in production environment
|
devise_scope :super_admin do
|
||||||
if Rails.env.production?
|
get 'super_admin/logout', to: 'super_admin/devise/sessions#destroy'
|
||||||
Sidekiq::Web.use Rack::Auth::Basic do |username, password|
|
namespace :super_admin do
|
||||||
ENV['SIDEKIQ_AUTH_USERNAME'] &&
|
resources :users
|
||||||
ENV['SIDEKIQ_AUTH_PASSWORD'] &&
|
resources :accounts
|
||||||
ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(username),
|
resources :super_admins
|
||||||
::Digest::SHA256.hexdigest(ENV['SIDEKIQ_AUTH_USERNAME'])) &&
|
resources :access_tokens
|
||||||
ActiveSupport::SecurityUtils.secure_compare(::Digest::SHA256.hexdigest(password),
|
|
||||||
::Digest::SHA256.hexdigest(ENV['SIDEKIQ_AUTH_PASSWORD']))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
mount Sidekiq::Web, at: '/sidekiq'
|
root to: 'users#index'
|
||||||
|
end
|
||||||
|
authenticated :super_admin do
|
||||||
|
mount Sidekiq::Web => '/monitoring/sidekiq'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
|
|
45
db/migrate/20200410145519_devise_create_super_admins.rb
Normal file
45
db/migrate/20200410145519_devise_create_super_admins.rb
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class DeviseCreateSuperAdmins < ActiveRecord::Migration[6.0]
|
||||||
|
def change
|
||||||
|
return if ActiveRecord::Base.connection.table_exists? 'super_admins'
|
||||||
|
|
||||||
|
create_table :super_admins do |t|
|
||||||
|
## Database authenticatable
|
||||||
|
t.string :email, null: false, default: ''
|
||||||
|
t.string :encrypted_password, null: false, default: ''
|
||||||
|
|
||||||
|
## Recoverable
|
||||||
|
# t.string :reset_password_token
|
||||||
|
# t.datetime :reset_password_sent_at
|
||||||
|
|
||||||
|
## Rememberable
|
||||||
|
t.datetime :remember_created_at
|
||||||
|
|
||||||
|
## Trackable
|
||||||
|
t.integer :sign_in_count, default: 0, null: false
|
||||||
|
t.datetime :current_sign_in_at
|
||||||
|
t.datetime :last_sign_in_at
|
||||||
|
t.inet :current_sign_in_ip
|
||||||
|
t.inet :last_sign_in_ip
|
||||||
|
|
||||||
|
## Confirmable
|
||||||
|
# t.string :confirmation_token
|
||||||
|
# t.datetime :confirmed_at
|
||||||
|
# t.datetime :confirmation_sent_at
|
||||||
|
# t.string :unconfirmed_email # Only if using reconfirmable
|
||||||
|
|
||||||
|
## Lockable
|
||||||
|
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
|
||||||
|
# t.string :unlock_token # Only if unlock strategy is :email or :both
|
||||||
|
# t.datetime :locked_at
|
||||||
|
|
||||||
|
t.timestamps null: false
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :super_admins, :email, unique: true
|
||||||
|
# add_index :super_admins, :reset_password_token, unique: true
|
||||||
|
# add_index :super_admins, :confirmation_token, unique: true
|
||||||
|
# add_index :super_admins, :unlock_token, unique: true
|
||||||
|
end
|
||||||
|
end
|
14
db/schema.rb
14
db/schema.rb
|
@ -327,6 +327,20 @@ ActiveRecord::Schema.define(version: 2020_05_10_112339) do
|
||||||
t.boolean "payment_source_added", default: false
|
t.boolean "payment_source_added", default: false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "super_admins", force: :cascade do |t|
|
||||||
|
t.string "email", default: "", null: false
|
||||||
|
t.string "encrypted_password", default: "", null: false
|
||||||
|
t.datetime "remember_created_at"
|
||||||
|
t.integer "sign_in_count", default: 0, null: false
|
||||||
|
t.datetime "current_sign_in_at"
|
||||||
|
t.datetime "last_sign_in_at"
|
||||||
|
t.inet "current_sign_in_ip"
|
||||||
|
t.inet "last_sign_in_ip"
|
||||||
|
t.datetime "created_at", precision: 6, null: false
|
||||||
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
|
t.index ["email"], name: "index_super_admins_on_email", unique: true
|
||||||
|
end
|
||||||
|
|
||||||
create_table "taggings", id: :serial, force: :cascade do |t|
|
create_table "taggings", id: :serial, force: :cascade do |t|
|
||||||
t.integer "tag_id"
|
t.integer "tag_id"
|
||||||
t.string "taggable_type"
|
t.string "taggable_type"
|
||||||
|
|
|
@ -2,9 +2,10 @@ require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe 'Accounts API', type: :request do
|
RSpec.describe 'Accounts API', type: :request do
|
||||||
describe 'POST /api/v1/accounts' do
|
describe 'POST /api/v1/accounts' do
|
||||||
|
let(:email) { Faker::Internet.email }
|
||||||
|
|
||||||
context 'when posting to accounts with correct parameters' do
|
context 'when posting to accounts with correct parameters' do
|
||||||
let(:account_builder) { double }
|
let(:account_builder) { double }
|
||||||
let(:email) { Faker::Internet.email }
|
|
||||||
let(:account) { create(:account) }
|
let(:account) { create(:account) }
|
||||||
let(:user) { create(:user, email: email, account: account) }
|
let(:user) { create(:user, email: email, account: account) }
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ RSpec.describe 'Accounts API', type: :request do
|
||||||
params: params,
|
params: params,
|
||||||
as: :json
|
as: :json
|
||||||
|
|
||||||
expect(AccountBuilder).to have_received(:new).with(params)
|
expect(AccountBuilder).to have_received(:new).with(params.merge(confirmed: false))
|
||||||
expect(account_builder).to have_received(:perform)
|
expect(account_builder).to have_received(:perform)
|
||||||
expect(response.headers.keys).to include('access-token', 'token-type', 'client', 'expiry', 'uid')
|
expect(response.headers.keys).to include('access-token', 'token-type', 'client', 'expiry', 'uid')
|
||||||
end
|
end
|
||||||
|
@ -36,16 +37,45 @@ RSpec.describe 'Accounts API', type: :request do
|
||||||
params: params,
|
params: params,
|
||||||
as: :json
|
as: :json
|
||||||
|
|
||||||
expect(AccountBuilder).to have_received(:new).with(params)
|
expect(AccountBuilder).to have_received(:new).with(params.merge(confirmed: false))
|
||||||
|
expect(account_builder).to have_received(:perform)
|
||||||
|
expect(response).to have_http_status(:forbidden)
|
||||||
|
expect(response.body).to eq({ message: I18n.t('errors.signup.failed') }.to_json)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'ignores confirmed param when called with out super admin token' do
|
||||||
|
allow(account_builder).to receive(:perform).and_return(nil)
|
||||||
|
|
||||||
|
params = { account_name: 'test', email: email, confirmed: true }
|
||||||
|
|
||||||
|
post api_v1_accounts_url,
|
||||||
|
params: params,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(AccountBuilder).to have_received(:new).with(params.merge(confirmed: false))
|
||||||
expect(account_builder).to have_received(:perform)
|
expect(account_builder).to have_received(:perform)
|
||||||
expect(response).to have_http_status(:forbidden)
|
expect(response).to have_http_status(:forbidden)
|
||||||
expect(response.body).to eq({ message: I18n.t('errors.signup.failed') }.to_json)
|
expect(response.body).to eq({ message: I18n.t('errors.signup.failed') }.to_json)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when ENABLE_ACCOUNT_SIGNUP env variable is set to false' do
|
context 'when called with super admin token' do
|
||||||
let(:email) { Faker::Internet.email }
|
let(:super_admin) { create(:super_admin) }
|
||||||
|
|
||||||
|
it 'calls account builder with confirmed true when confirmed param is passed' do
|
||||||
|
params = { account_name: 'test', email: email, confirmed: true }
|
||||||
|
|
||||||
|
post api_v1_accounts_url,
|
||||||
|
params: params,
|
||||||
|
headers: { api_access_token: super_admin.access_token.token },
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(User.find_by(email: email).confirmed?).to eq(true)
|
||||||
|
expect(response.headers.keys).to include('access-token', 'token-type', 'client', 'expiry', 'uid')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when ENABLE_ACCOUNT_SIGNUP env variable is set to false' do
|
||||||
it 'responds 404 on requests' do
|
it 'responds 404 on requests' do
|
||||||
params = { account_name: 'test', email: email }
|
params = { account_name: 'test', email: email }
|
||||||
ENV['ENABLE_ACCOUNT_SIGNUP'] = 'false'
|
ENV['ENABLE_ACCOUNT_SIGNUP'] = 'false'
|
||||||
|
@ -60,8 +90,6 @@ RSpec.describe 'Accounts API', type: :request do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when ENABLE_ACCOUNT_SIGNUP env variable is set to api_only' do
|
context 'when ENABLE_ACCOUNT_SIGNUP env variable is set to api_only' do
|
||||||
let(:email) { Faker::Internet.email }
|
|
||||||
|
|
||||||
it 'does not respond 404 on requests' do
|
it 'does not respond 404 on requests' do
|
||||||
params = { account_name: 'test', email: email }
|
params = { account_name: 'test', email: email }
|
||||||
ENV['ENABLE_ACCOUNT_SIGNUP'] = 'api_only'
|
ENV['ENABLE_ACCOUNT_SIGNUP'] = 'api_only'
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Super Admin access tokens API', type: :request do
|
||||||
|
let(:super_admin) { create(:super_admin) }
|
||||||
|
|
||||||
|
describe 'GET /super_admin/access_tokens' do
|
||||||
|
context 'when it is an unauthenticated super admin' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
get '/super_admin/'
|
||||||
|
expect(response).to have_http_status(:redirect)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated super admin' do
|
||||||
|
it 'shows the list of access tokens' do
|
||||||
|
sign_in super_admin
|
||||||
|
get '/super_admin/access_tokens'
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.body).to include('New access token')
|
||||||
|
expect(response.body).to include(super_admin.access_token.token)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
26
spec/controllers/super_admin/accounts_controller_spec.rb
Normal file
26
spec/controllers/super_admin/accounts_controller_spec.rb
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Super Admin accounts API', type: :request do
|
||||||
|
let(:super_admin) { create(:super_admin) }
|
||||||
|
|
||||||
|
describe 'GET /super_admin/accounts' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
get '/super_admin/accounts'
|
||||||
|
expect(response).to have_http_status(:redirect)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
let!(:account) { create(:account) }
|
||||||
|
|
||||||
|
it 'shows the list of accounts' do
|
||||||
|
sign_in super_admin
|
||||||
|
get '/super_admin/accounts'
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.body).to include('New account')
|
||||||
|
expect(response.body).to include(account.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
24
spec/controllers/super_admin/super_admins_controller_spec.rb
Normal file
24
spec/controllers/super_admin/super_admins_controller_spec.rb
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Super Admin super admins API', type: :request do
|
||||||
|
let(:super_admin) { create(:super_admin) }
|
||||||
|
|
||||||
|
describe 'GET /super_admin/users' do
|
||||||
|
context 'when it is an unauthenticated super admin' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
get '/super_admin/super_admins'
|
||||||
|
expect(response).to have_http_status(:redirect)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated super admin' do
|
||||||
|
it 'shows the list of super admins' do
|
||||||
|
sign_in super_admin
|
||||||
|
get '/super_admin/super_admins'
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.body).to include('New super admin')
|
||||||
|
expect(response.body).to include(super_admin.email)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
26
spec/controllers/super_admin/users_controller_spec.rb
Normal file
26
spec/controllers/super_admin/users_controller_spec.rb
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Super Admin Users API', type: :request do
|
||||||
|
let(:super_admin) { create(:super_admin) }
|
||||||
|
|
||||||
|
describe 'GET /super_admin/users' do
|
||||||
|
context 'when it is an unauthenticated super admin' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
get '/super_admin/'
|
||||||
|
expect(response).to have_http_status(:redirect)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated super admin' do
|
||||||
|
let!(:user) { create(:user) }
|
||||||
|
|
||||||
|
it 'shows the list of users' do
|
||||||
|
sign_in super_admin
|
||||||
|
get '/super_admin'
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.body).to include('New user')
|
||||||
|
expect(response.body).to include(user.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
46
spec/controllers/super_admin_controller_spec.rb
Normal file
46
spec/controllers/super_admin_controller_spec.rb
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Super Admin', type: :request do
|
||||||
|
let(:super_admin) { create(:super_admin) }
|
||||||
|
|
||||||
|
describe 'request to /super_admin' do
|
||||||
|
context 'when the super admin is unauthenticated' do
|
||||||
|
it 'redirects to signin page' do
|
||||||
|
get '/super_admin/'
|
||||||
|
expect(response).to have_http_status(:redirect)
|
||||||
|
expect(response.body).to include('sign_in')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'signs super admin in and out' do
|
||||||
|
sign_in super_admin
|
||||||
|
get '/super_admin'
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.body).to include('New user')
|
||||||
|
|
||||||
|
sign_out super_admin
|
||||||
|
get '/super_admin'
|
||||||
|
expect(response).to have_http_status(:redirect)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'request to /super_admin/sidekiq' do
|
||||||
|
context 'when the super admin is unauthenticated' do
|
||||||
|
it 'redirects to signin page' do
|
||||||
|
get '/monitoring/sidekiq'
|
||||||
|
expect(response).to have_http_status(:not_found)
|
||||||
|
expect(response.body).to include('sign_in')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'signs super admin in and out' do
|
||||||
|
sign_in super_admin
|
||||||
|
get '/monitoring/sidekiq'
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
|
||||||
|
sign_out super_admin
|
||||||
|
get '/monitoring/sidekiq'
|
||||||
|
expect(response).to have_http_status(:not_found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
6
spec/factories/super_admins.rb
Normal file
6
spec/factories/super_admins.rb
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
FactoryBot.define do
|
||||||
|
factory :super_admin do
|
||||||
|
email { "admin@#{SecureRandom.uuid}.com" }
|
||||||
|
password { 'password' }
|
||||||
|
end
|
||||||
|
end
|
5
spec/models/super_admin_spec.rb
Normal file
5
spec/models/super_admin_spec.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SuperAdmin, type: :model do
|
||||||
|
pending "add some examples to (or delete) #{__FILE__}"
|
||||||
|
end
|
|
@ -61,6 +61,8 @@ RSpec.configure do |config|
|
||||||
config.filter_rails_from_backtrace!
|
config.filter_rails_from_backtrace!
|
||||||
# arbitrary gems may also be filtered via:
|
# arbitrary gems may also be filtered via:
|
||||||
# config.filter_gems_from_backtrace("gem name")
|
# config.filter_gems_from_backtrace("gem name")
|
||||||
|
|
||||||
|
config.include Devise::Test::IntegrationHelpers, type: :request
|
||||||
end
|
end
|
||||||
|
|
||||||
Shoulda::Matchers.configure do |config|
|
Shoulda::Matchers.configure do |config|
|
||||||
|
|
Loading…
Reference in a new issue