mirror of
https://github.com/astuto/astuto.git
synced 2025-12-15 03:07:52 +01:00
Add users management to site settings (#126)
This commit is contained in:
committed by
GitHub
parent
bc15140512
commit
37fb99a868
5
Gemfile
5
Gemfile
@@ -8,8 +8,8 @@ gem 'rails', '6.0.5'
|
||||
gem 'pg', '>= 0.18', '< 2.0'
|
||||
|
||||
gem 'puma', '~> 4.3'
|
||||
|
||||
gem 'sass-rails', '~> 5'
|
||||
gem 'sassc', '2.1.0' # temporarely, because 2.4.0 takes 5 minutes to install...
|
||||
|
||||
gem 'webpacker', '4.3.0'
|
||||
|
||||
@@ -28,9 +28,6 @@ gem 'pundit', '2.2.0'
|
||||
# I18n (forward locales to JS)
|
||||
gem 'i18n-js'
|
||||
|
||||
# Administration panel
|
||||
gem "administrate", '0.16.0'
|
||||
|
||||
# React
|
||||
gem 'react-rails', '~> 2.6.0'
|
||||
|
||||
|
||||
29
Gemfile.lock
29
Gemfile.lock
@@ -58,16 +58,6 @@ GEM
|
||||
zeitwerk (~> 2.2, >= 2.2.2)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
administrate (0.16.0)
|
||||
actionpack (>= 5.0)
|
||||
actionview (>= 5.0)
|
||||
activerecord (>= 5.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)
|
||||
babel-source (5.8.35)
|
||||
babel-transpiler (0.7.0)
|
||||
babel-source (>= 4.0, < 6)
|
||||
@@ -91,8 +81,6 @@ GEM
|
||||
concurrent-ruby (1.1.10)
|
||||
connection_pool (2.2.5)
|
||||
crass (1.0.6)
|
||||
datetime_picker_rails (0.0.7)
|
||||
momentjs-rails (>= 2.8.1)
|
||||
devise (4.7.3)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
@@ -117,10 +105,6 @@ GEM
|
||||
jbuilder (2.11.5)
|
||||
actionview (>= 5.0.0)
|
||||
activesupport (>= 5.0.0)
|
||||
jquery-rails (4.5.0)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
kaminari (1.2.2)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.2.2)
|
||||
@@ -148,8 +132,6 @@ GEM
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.8.0)
|
||||
minitest (5.15.0)
|
||||
momentjs-rails (2.29.1.1)
|
||||
railties (>= 3.1)
|
||||
msgpack (1.5.2)
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.13.6)
|
||||
@@ -239,15 +221,6 @@ GEM
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
tilt (>= 1.1, < 3)
|
||||
sassc (2.1.0)
|
||||
ffi (~> 1.9)
|
||||
sassc-rails (2.1.2)
|
||||
railties (>= 4.0.0)
|
||||
sassc (>= 2.0)
|
||||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
selectize-rails (0.12.6)
|
||||
selenium-webdriver (4.1.0)
|
||||
childprocess (>= 0.5, < 5.0)
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
@@ -297,7 +270,6 @@ PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
administrate (= 0.16.0)
|
||||
bootsnap (>= 1.4.2)
|
||||
byebug
|
||||
capybara (>= 2.15)
|
||||
@@ -314,7 +286,6 @@ DEPENDENCIES
|
||||
react-rails (~> 2.6.0)
|
||||
rspec-rails (~> 3.8.2)
|
||||
sass-rails (~> 5)
|
||||
sassc (= 2.1.0)
|
||||
selenium-webdriver
|
||||
spring
|
||||
spring-watcher-listen (~> 2.0.0)
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# All Administrate controllers inherit from this `Admin::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.
|
||||
module Admin
|
||||
class ApplicationController < Administrate::ApplicationController
|
||||
include ApplicationHelper
|
||||
|
||||
before_action :authenticate_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
|
||||
end
|
||||
@@ -1,14 +0,0 @@
|
||||
module Admin
|
||||
class BoardsController < Admin::ApplicationController
|
||||
before_action :default_order
|
||||
|
||||
def default_order
|
||||
@order ||= Administrate::Order.new(
|
||||
params.fetch(resource_name, {}).fetch(:order, 'order'),
|
||||
params.fetch(resource_name, {}).fetch(:direction, 'asc'),
|
||||
)
|
||||
end
|
||||
|
||||
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
||||
end
|
||||
end
|
||||
@@ -1,15 +0,0 @@
|
||||
module Admin
|
||||
class CommentsController < Admin::ApplicationController
|
||||
before_action :default_order
|
||||
|
||||
def default_order
|
||||
@order ||= Administrate::Order.new(
|
||||
params.fetch(resource_name, {}).fetch(:order, 'updated_at'),
|
||||
params.fetch(resource_name, {}).fetch(:direction, 'desc'),
|
||||
)
|
||||
end
|
||||
|
||||
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
||||
@@ -1,14 +0,0 @@
|
||||
module Admin
|
||||
class PostStatusesController < Admin::ApplicationController
|
||||
before_action :default_order
|
||||
|
||||
def default_order
|
||||
@order ||= Administrate::Order.new(
|
||||
params.fetch(resource_name, {}).fetch(:order, 'order'),
|
||||
params.fetch(resource_name, {}).fetch(:direction, 'asc'),
|
||||
)
|
||||
end
|
||||
|
||||
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
||||
end
|
||||
end
|
||||
@@ -1,15 +0,0 @@
|
||||
module Admin
|
||||
class PostsController < Admin::ApplicationController
|
||||
before_action :default_order
|
||||
|
||||
def default_order
|
||||
@order ||= Administrate::Order.new(
|
||||
params.fetch(resource_name, {}).fetch(:order, 'updated_at'),
|
||||
params.fetch(resource_name, {}).fetch(:direction, 'desc'),
|
||||
)
|
||||
end
|
||||
|
||||
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
||||
# for more information
|
||||
end
|
||||
end
|
||||
@@ -1,69 +0,0 @@
|
||||
module Admin
|
||||
class UsersController < Admin::ApplicationController
|
||||
before_action :default_order
|
||||
|
||||
def default_order
|
||||
@order ||= Administrate::Order.new(
|
||||
params.fetch(resource_name, {}).fetch(:order, 'updated_at'),
|
||||
params.fetch(resource_name, {}).fetch(:direction, 'desc'),
|
||||
)
|
||||
end
|
||||
|
||||
def authenticate_admin
|
||||
unless user_signed_in?
|
||||
flash[:alert] = t('backend.errors.not_logged_in')
|
||||
redirect_to new_user_session_path
|
||||
return
|
||||
end
|
||||
|
||||
unless current_user.admin?
|
||||
flash[:alert] = t('backend.errors.not_enough_privileges')
|
||||
redirect_to root_path
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# overwrite default create
|
||||
def create
|
||||
user = User.new(user_params)
|
||||
user.skip_confirmation! # automatically confirm user email
|
||||
|
||||
if user.save
|
||||
flash[:notice] = translate_with_resource('create.success')
|
||||
redirect_to admin_user_path(user)
|
||||
else
|
||||
render :new, locals: {
|
||||
page: Administrate::Page::Form.new(dashboard, user),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# overwrite default update
|
||||
def update
|
||||
user = User.find(params[:id])
|
||||
|
||||
if params[:user][:password].empty?
|
||||
user.assign_attributes(user_params.except(:password))
|
||||
else
|
||||
user.assign_attributes(user_params)
|
||||
end
|
||||
|
||||
user.skip_reconfirmation! # automatically reconfirm user email
|
||||
|
||||
if user.save
|
||||
flash[:notice] = translate_with_resource('update.success')
|
||||
redirect_to admin_user_path(user)
|
||||
else
|
||||
render :new, locals: {
|
||||
page: Administrate::Page::Form.new(dashboard, user),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:full_name, :email, :role, :password)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -9,8 +9,10 @@ class ApplicationController < ActionController::Base
|
||||
protected
|
||||
|
||||
def configure_permitted_parameters
|
||||
devise_parameter_sanitizer.permit(:sign_up, keys: [:full_name, :notifications_enabled])
|
||||
devise_parameter_sanitizer.permit(:account_update, keys: [:full_name, :notifications_enabled])
|
||||
additional_permitted_parameters = [:full_name, :notifications_enabled]
|
||||
|
||||
devise_parameter_sanitizer.permit(:sign_up, keys: additional_permitted_parameters)
|
||||
devise_parameter_sanitizer.permit(:account_update, keys: additional_permitted_parameters)
|
||||
end
|
||||
|
||||
def load_boards
|
||||
|
||||
11
app/controllers/registrations_controller.rb
Normal file
11
app/controllers/registrations_controller.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class RegistrationsController < Devise::RegistrationsController
|
||||
# Override destroy to soft delete
|
||||
def destroy
|
||||
resource.status = "deleted"
|
||||
resource.save
|
||||
Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
|
||||
set_flash_message :notice, :destroyed
|
||||
yield resource if block_given?
|
||||
respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) }
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,8 @@
|
||||
class SiteSettingsController < ApplicationController
|
||||
include ApplicationHelper
|
||||
|
||||
before_action :authenticate_admin
|
||||
before_action :authenticate_admin, only: [:general, :boards, :post_statuses, :roadmap]
|
||||
before_action :authenticate_power_user, only: [:users]
|
||||
|
||||
def general
|
||||
end
|
||||
@@ -14,4 +15,7 @@ class SiteSettingsController < ApplicationController
|
||||
|
||||
def roadmap
|
||||
end
|
||||
|
||||
def users
|
||||
end
|
||||
end
|
||||
36
app/controllers/users_controller.rb
Normal file
36
app/controllers/users_controller.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
class UsersController < ApplicationController
|
||||
before_action :authenticate_user!, only: [:index, :update]
|
||||
|
||||
def index
|
||||
authorize User
|
||||
|
||||
@users = User
|
||||
.all
|
||||
.order(role: :desc)
|
||||
|
||||
render json: @users
|
||||
end
|
||||
|
||||
def update
|
||||
@user = User.find(params[:id])
|
||||
authorize @user
|
||||
|
||||
@user.assign_attributes user_update_params
|
||||
|
||||
if @user.save
|
||||
render json: @user
|
||||
else
|
||||
render json: {
|
||||
error: @user.errors.full_messages
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_update_params
|
||||
params
|
||||
.require(:user)
|
||||
.permit(policy(@user).permitted_attributes_for_update)
|
||||
end
|
||||
end
|
||||
@@ -1,71 +0,0 @@
|
||||
require "administrate/base_dashboard"
|
||||
|
||||
class BoardDashboard < 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,
|
||||
description: Field::Text,
|
||||
order: Field::Number,
|
||||
posts: Field::HasMany,
|
||||
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[
|
||||
name
|
||||
description
|
||||
order
|
||||
posts
|
||||
].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = %i[
|
||||
id
|
||||
name
|
||||
description
|
||||
order
|
||||
posts
|
||||
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
|
||||
description
|
||||
order
|
||||
].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) { where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how boards are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
def display_resource(board)
|
||||
"Board \"#{board.name}\""
|
||||
end
|
||||
end
|
||||
@@ -1,76 +0,0 @@
|
||||
require "administrate/base_dashboard"
|
||||
|
||||
class CommentDashboard < 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,
|
||||
body: Field::Text,
|
||||
parent_id: Field::Number,
|
||||
parent: Field::BelongsTo.with_options(class_name: "Comment"),
|
||||
children: Field::HasMany.with_options(class_name: "Comment"),
|
||||
user: Field::BelongsTo,
|
||||
post: Field::BelongsTo,
|
||||
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[
|
||||
body
|
||||
user
|
||||
post
|
||||
children
|
||||
].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = %i[
|
||||
id
|
||||
body
|
||||
parent_id
|
||||
parent
|
||||
children
|
||||
user
|
||||
post
|
||||
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[
|
||||
body
|
||||
parent
|
||||
user
|
||||
post
|
||||
].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) { where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how comments are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
# def display_resource(comment)
|
||||
# "Comment ##{comment.id}"
|
||||
# end
|
||||
end
|
||||
@@ -1,79 +0,0 @@
|
||||
require "administrate/base_dashboard"
|
||||
|
||||
class PostDashboard < 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,
|
||||
title: Field::String,
|
||||
description: Field::Text,
|
||||
comments: Field::HasMany,
|
||||
user: Field::BelongsTo,
|
||||
board: Field::BelongsTo,
|
||||
post_status: Field::BelongsTo,
|
||||
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[
|
||||
title
|
||||
description
|
||||
user
|
||||
board
|
||||
post_status
|
||||
comments
|
||||
].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = %i[
|
||||
id
|
||||
title
|
||||
description
|
||||
user
|
||||
board
|
||||
post_status
|
||||
comments
|
||||
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[
|
||||
title
|
||||
description
|
||||
user
|
||||
board
|
||||
post_status
|
||||
].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) { where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how posts are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
# def display_resource(post)
|
||||
# "Post ##{post.id}"
|
||||
# end
|
||||
end
|
||||
@@ -1,75 +0,0 @@
|
||||
require "administrate/base_dashboard"
|
||||
|
||||
class PostStatusDashboard < 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,
|
||||
color: ColorField,
|
||||
order: Field::Number,
|
||||
show_in_roadmap: Field::Boolean,
|
||||
posts: Field::HasMany,
|
||||
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[
|
||||
name
|
||||
color
|
||||
order
|
||||
show_in_roadmap
|
||||
posts
|
||||
].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = %i[
|
||||
id
|
||||
name
|
||||
color
|
||||
order
|
||||
show_in_roadmap
|
||||
posts
|
||||
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
|
||||
color
|
||||
order
|
||||
show_in_roadmap
|
||||
].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) { where(open: true) }
|
||||
# }.freeze
|
||||
COLLECTION_FILTERS = {}.freeze
|
||||
|
||||
# Overwrite this method to customize how post statuses are displayed
|
||||
# across all pages of the admin dashboard.
|
||||
#
|
||||
def display_resource(post_status)
|
||||
"PostStatus \"#{post_status.name}\""
|
||||
end
|
||||
end
|
||||
@@ -1,88 +0,0 @@
|
||||
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 = {
|
||||
id: Field::Number,
|
||||
email: Field::String,
|
||||
password: Field::Password,
|
||||
encrypted_password: Field::String,
|
||||
reset_password_token: Field::String,
|
||||
reset_password_sent_at: Field::DateTime,
|
||||
remember_created_at: Field::DateTime,
|
||||
confirmation_token: Field::String,
|
||||
confirmed_at: Field::DateTime,
|
||||
confirmation_sent_at: Field::DateTime,
|
||||
unconfirmed_email: Field::String,
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime,
|
||||
role: RoleField,
|
||||
full_name: Field::String,
|
||||
posts: Field::HasMany,
|
||||
comments: Field::HasMany,
|
||||
}.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[
|
||||
full_name
|
||||
email
|
||||
role
|
||||
posts
|
||||
comments
|
||||
].freeze
|
||||
|
||||
# SHOW_PAGE_ATTRIBUTES
|
||||
# an array of attributes that will be displayed on the model's show page.
|
||||
SHOW_PAGE_ATTRIBUTES = %i[
|
||||
id
|
||||
full_name
|
||||
email
|
||||
role
|
||||
password
|
||||
posts
|
||||
comments
|
||||
created_at
|
||||
updated_at
|
||||
confirmed_at
|
||||
confirmation_sent_at
|
||||
unconfirmed_email
|
||||
].freeze
|
||||
|
||||
# FORM_ATTRIBUTES
|
||||
# an array of attributes that will be displayed
|
||||
# on the model's form (`new` and `edit`) pages.
|
||||
FORM_ATTRIBUTES = %i[
|
||||
full_name
|
||||
email
|
||||
role
|
||||
password
|
||||
].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) { 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.role.capitalize} \"#{user.full_name}\""
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
require "administrate/field/base"
|
||||
|
||||
class ColorField < Administrate::Field::Base
|
||||
def to_s
|
||||
data.to_s
|
||||
end
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
require "administrate/field/base"
|
||||
|
||||
class IdField < Administrate::Field::Base
|
||||
def to_s
|
||||
data.to_s
|
||||
end
|
||||
end
|
||||
@@ -1,13 +0,0 @@
|
||||
require "administrate/field/base"
|
||||
|
||||
class RoleField < Administrate::Field::Base
|
||||
def to_s
|
||||
data.to_s
|
||||
end
|
||||
|
||||
def select_field_values(form_builder)
|
||||
form_builder.object.class.public_send(attribute.to_s.pluralize).keys.map do |v|
|
||||
[v.titleize, v]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,12 +1,27 @@
|
||||
module ApplicationHelper
|
||||
def authenticate_admin
|
||||
def check_user_signed_in
|
||||
unless user_signed_in?
|
||||
flash[:alert] = t('backend.errors.not_logged_in')
|
||||
redirect_to new_user_session_path
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate_admin
|
||||
return if check_user_signed_in == false
|
||||
|
||||
unless current_user.admin?
|
||||
flash[:alert] = t('backend.errors.not_enough_privileges')
|
||||
redirect_to root_path
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
unless current_user.moderator? || current_user.admin?
|
||||
def authenticate_power_user
|
||||
return if check_user_signed_in == false
|
||||
|
||||
unless current_user.admin? or current_user.moderator?
|
||||
flash[:alert] = t('backend.errors.not_enough_privileges')
|
||||
redirect_to root_path
|
||||
return
|
||||
|
||||
60
app/javascript/actions/User/requestUsers.ts
Normal file
60
app/javascript/actions/User/requestUsers.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Action } from 'redux';
|
||||
import { ThunkAction } from 'redux-thunk';
|
||||
|
||||
import IUserJSON from '../../interfaces/json/IUser';
|
||||
|
||||
import { State } from '../../reducers/rootReducer';
|
||||
|
||||
export const USERS_REQUEST_START = 'USERS_REQUEST_START';
|
||||
interface UsersRequestStartAction {
|
||||
type: typeof USERS_REQUEST_START;
|
||||
}
|
||||
|
||||
export const USERS_REQUEST_SUCCESS = 'USERS_REQUEST_SUCCESS';
|
||||
interface UsersRequestSuccessAction {
|
||||
type: typeof USERS_REQUEST_SUCCESS;
|
||||
users: Array<IUserJSON>;
|
||||
}
|
||||
|
||||
export const USERS_REQUEST_FAILURE = 'USERS_REQUEST_FAILURE';
|
||||
interface UsersRequestFailureAction {
|
||||
type: typeof USERS_REQUEST_FAILURE;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export type UsersRequestActionTypes =
|
||||
UsersRequestStartAction |
|
||||
UsersRequestSuccessAction |
|
||||
UsersRequestFailureAction;
|
||||
|
||||
|
||||
const usersRequestStart = (): UsersRequestActionTypes => ({
|
||||
type: USERS_REQUEST_START,
|
||||
});
|
||||
|
||||
const usersRequestSuccess = (
|
||||
users: Array<IUserJSON>
|
||||
): UsersRequestActionTypes => ({
|
||||
type: USERS_REQUEST_SUCCESS,
|
||||
users,
|
||||
});
|
||||
|
||||
const usersRequestFailure = (error: string): UsersRequestActionTypes => ({
|
||||
type: USERS_REQUEST_FAILURE,
|
||||
error,
|
||||
});
|
||||
|
||||
export const requestUsers = (): ThunkAction<void, State, null, Action<string>> => (
|
||||
async (dispatch) => {
|
||||
dispatch(usersRequestStart());
|
||||
|
||||
try {
|
||||
const response = await fetch('/users');
|
||||
const json = await response.json();
|
||||
|
||||
dispatch(usersRequestSuccess(json));
|
||||
} catch (e) {
|
||||
dispatch(usersRequestFailure(e));
|
||||
}
|
||||
}
|
||||
)
|
||||
87
app/javascript/actions/User/updateUser.ts
Normal file
87
app/javascript/actions/User/updateUser.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { Action } from "redux";
|
||||
import { ThunkAction } from "redux-thunk";
|
||||
|
||||
import HttpStatus from "../../constants/http_status";
|
||||
import buildRequestHeaders from "../../helpers/buildRequestHeaders";
|
||||
import IUserJSON from "../../interfaces/json/IUser";
|
||||
import { State } from "../../reducers/rootReducer";
|
||||
|
||||
export const USER_UPDATE_START = 'USER_UPDATE_START';
|
||||
interface UserUpdateStartAction {
|
||||
type: typeof USER_UPDATE_START;
|
||||
}
|
||||
|
||||
export const USER_UPDATE_SUCCESS = 'USER_UPDATE_SUCCESS';
|
||||
interface UserUpdateSuccessAction {
|
||||
type: typeof USER_UPDATE_SUCCESS;
|
||||
user: IUserJSON;
|
||||
}
|
||||
|
||||
export const USER_UPDATE_FAILURE = 'USER_UPDATE_FAILURE';
|
||||
interface UserUpdateFailureAction {
|
||||
type: typeof USER_UPDATE_FAILURE;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export type UserUpdateActionTypes =
|
||||
UserUpdateStartAction |
|
||||
UserUpdateSuccessAction |
|
||||
UserUpdateFailureAction;
|
||||
|
||||
const userUpdateStart = (): UserUpdateStartAction => ({
|
||||
type: USER_UPDATE_START,
|
||||
});
|
||||
|
||||
const userUpdateSuccess = (
|
||||
userJSON: IUserJSON,
|
||||
): UserUpdateSuccessAction => ({
|
||||
type: USER_UPDATE_SUCCESS,
|
||||
user: userJSON,
|
||||
});
|
||||
|
||||
const userUpdateFailure = (error: string): UserUpdateFailureAction => ({
|
||||
type: USER_UPDATE_FAILURE,
|
||||
error,
|
||||
});
|
||||
|
||||
interface UpdateUserParams {
|
||||
id: number;
|
||||
role?: string;
|
||||
status?: string;
|
||||
authenticityToken: string;
|
||||
}
|
||||
|
||||
export const updateUser = ({
|
||||
id,
|
||||
role = null,
|
||||
status = null,
|
||||
authenticityToken,
|
||||
}: UpdateUserParams): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
|
||||
dispatch(userUpdateStart());
|
||||
|
||||
const user = Object.assign({},
|
||||
role !== null ? { role } : null,
|
||||
status !== null ? { status } : null,
|
||||
);
|
||||
|
||||
try {
|
||||
const res = await fetch(`/users/${id}`, {
|
||||
method: 'PATCH',
|
||||
headers: buildRequestHeaders(authenticityToken),
|
||||
body: JSON.stringify({ user }),
|
||||
});
|
||||
const json = await res.json();
|
||||
|
||||
if (res.status === HttpStatus.OK) {
|
||||
dispatch(userUpdateSuccess(json));
|
||||
} else {
|
||||
dispatch(userUpdateFailure(json.error));
|
||||
}
|
||||
|
||||
return Promise.resolve(res);
|
||||
} catch (e) {
|
||||
dispatch(userUpdateFailure(e));
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
};
|
||||
@@ -92,7 +92,7 @@ class BoardsSiteSettingsP extends React.Component<Props> {
|
||||
return (
|
||||
<>
|
||||
<Box>
|
||||
<h2>{I18n.t('site_settings.boards.title')}</h2>
|
||||
<h2>{ I18n.t('site_settings.boards.title') }</h2>
|
||||
|
||||
{
|
||||
boards.items.length > 0 ?
|
||||
|
||||
157
app/javascript/components/SiteSettings/Users/UserEditable.tsx
Normal file
157
app/javascript/components/SiteSettings/Users/UserEditable.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import * as React from "react";
|
||||
import Gravatar from 'react-gravatar';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import IUser, { UserRoles, USER_ROLE_ADMIN, USER_ROLE_USER, USER_STATUS_ACTIVE, USER_STATUS_BLOCKED, USER_STATUS_DELETED } from "../../../interfaces/IUser";
|
||||
import Separator from "../../common/Separator";
|
||||
import UserForm from "./UserForm";
|
||||
import { MutedText } from "../../common/CustomTexts";
|
||||
|
||||
interface Props {
|
||||
user: IUser;
|
||||
updateUserRole(
|
||||
id: number,
|
||||
role: UserRoles,
|
||||
closeEditMode: Function,
|
||||
): void;
|
||||
updateUserStatus(
|
||||
id: number,
|
||||
status: typeof USER_STATUS_ACTIVE | typeof USER_STATUS_BLOCKED,
|
||||
): void;
|
||||
|
||||
currentUserRole: UserRoles;
|
||||
currentUserEmail: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
editMode: boolean;
|
||||
}
|
||||
|
||||
class UserEditable extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = { editMode: false };
|
||||
|
||||
this.toggleEditMode = this.toggleEditMode.bind(this);
|
||||
this._handleUpdateUserRole = this._handleUpdateUserRole.bind(this);
|
||||
this._handleUpdateUserStatus = this._handleUpdateUserStatus.bind(this);
|
||||
}
|
||||
|
||||
toggleEditMode() {
|
||||
this.setState({ editMode: !this.state.editMode });
|
||||
}
|
||||
|
||||
_handleUpdateUserRole(newRole: UserRoles) {
|
||||
this.props.updateUserRole(
|
||||
this.props.user.id,
|
||||
newRole,
|
||||
this.toggleEditMode,
|
||||
);
|
||||
}
|
||||
|
||||
_handleUpdateUserStatus() {
|
||||
const { user } = this.props;
|
||||
const currentStatus = user.status;
|
||||
let newStatus: typeof USER_STATUS_ACTIVE | typeof USER_STATUS_BLOCKED;
|
||||
|
||||
if (currentStatus === 'deleted') return;
|
||||
|
||||
if (currentStatus === 'active') newStatus = 'blocked';
|
||||
else newStatus = 'active';
|
||||
|
||||
const confirmationMessage =
|
||||
newStatus === 'blocked' ?
|
||||
I18n.t('site_settings.users.block_confirmation', { name: user.fullName })
|
||||
:
|
||||
I18n.t('site_settings.users.unblock_confirmation', { name: user.fullName });
|
||||
|
||||
const confirmationResponse = confirm(confirmationMessage);
|
||||
|
||||
if (confirmationResponse) {
|
||||
this.props.updateUserStatus(user.id, newStatus);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { user, currentUserRole, currentUserEmail } = this.props;
|
||||
const { editMode } = this.state;
|
||||
|
||||
const editEnabled =
|
||||
user.status === USER_STATUS_ACTIVE &&
|
||||
currentUserRole === USER_ROLE_ADMIN &&
|
||||
currentUserEmail !== user.email;
|
||||
|
||||
const blockEnabled =
|
||||
user.status !== USER_STATUS_DELETED &&
|
||||
(currentUserRole === USER_ROLE_ADMIN || user.role === USER_ROLE_USER) &&
|
||||
currentUserEmail !== user.email;
|
||||
|
||||
return (
|
||||
<li className="userEditable">
|
||||
{
|
||||
editMode === false ?
|
||||
<>
|
||||
<div className="userInfo">
|
||||
<Gravatar email={user.email} size={42} className="gravatar userGravatar" />
|
||||
|
||||
<div className="userFullNameRoleStatus">
|
||||
<span className="userFullName">{ user.fullName }</span>
|
||||
|
||||
<div className="userRoleStatus">
|
||||
<span>
|
||||
<MutedText>{ I18n.t(`site_settings.users.role_${user.role}`) }</MutedText>
|
||||
</span>
|
||||
|
||||
{
|
||||
user.status !== USER_STATUS_ACTIVE ?
|
||||
<>
|
||||
<Separator />
|
||||
<span className={`userStatus userStatus${user.status}`}>
|
||||
{ I18n.t(`site_settings.users.status_${user.status}`) }
|
||||
</span>
|
||||
</>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="userEditableActions">
|
||||
<a
|
||||
onClick={() => editEnabled && this.toggleEditMode()}
|
||||
className={editEnabled ? '' : 'actionDisabled'}
|
||||
>
|
||||
{ I18n.t('common.buttons.edit') }
|
||||
</a>
|
||||
|
||||
<Separator />
|
||||
|
||||
<a
|
||||
onClick={() => blockEnabled && this._handleUpdateUserStatus()}
|
||||
className={blockEnabled ? '' : 'actionDisabled'}
|
||||
>
|
||||
{
|
||||
user.status !== USER_STATUS_BLOCKED ?
|
||||
I18n.t('site_settings.users.block')
|
||||
:
|
||||
I18n.t('site_settings.users.unblock')
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
:
|
||||
<>
|
||||
<UserForm user={user} updateUserRole={this._handleUpdateUserRole} />
|
||||
<a onClick={this.toggleEditMode} className="userEditCancelButton">
|
||||
{ I18n.t('common.buttons.cancel') }
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserEditable;
|
||||
79
app/javascript/components/SiteSettings/Users/UserForm.tsx
Normal file
79
app/javascript/components/SiteSettings/Users/UserForm.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import * as React from 'react';
|
||||
import Gravatar from 'react-gravatar';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import Button from '../../common/Button';
|
||||
import IUser, { UserRoles, USER_ROLE_ADMIN, USER_ROLE_MODERATOR, USER_ROLE_USER } from '../../../interfaces/IUser';
|
||||
|
||||
interface Props {
|
||||
user: IUser;
|
||||
updateUserRole(newRole: UserRoles): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
role: UserRoles;
|
||||
}
|
||||
|
||||
class UserForm extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = { role: this.props.user.role };
|
||||
|
||||
this._handleUpdateUserRole = this._handleUpdateUserRole.bind(this);
|
||||
}
|
||||
|
||||
_handleUpdateUserRole(selectedRole: UserRoles) {
|
||||
const { user, updateUserRole } = this.props;
|
||||
let confirmation = true;
|
||||
|
||||
if (selectedRole === 'admin') {
|
||||
confirmation = confirm(I18n.t('site_settings.users.role_to_admin_confirmation', { name: user.fullName }));
|
||||
}
|
||||
|
||||
if (confirmation) updateUserRole(selectedRole);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { user } = this.props;
|
||||
const selectedRole = this.state.role;
|
||||
|
||||
return (
|
||||
<div className="userForm">
|
||||
<Gravatar email={user.email} size={42} className="gravatar userGravatar" />
|
||||
|
||||
<div className="userFullNameRoleForm">
|
||||
<span className="userFullName">{ user.fullName }</span>
|
||||
|
||||
<select
|
||||
value={selectedRole || 'Loading...'}
|
||||
onChange={
|
||||
(e: React.FormEvent) => {
|
||||
this.setState({role: (e.target as HTMLSelectElement).value as UserRoles});
|
||||
}}
|
||||
id="selectPickerUserRole"
|
||||
className="selectPicker"
|
||||
>
|
||||
<optgroup label="Roles">
|
||||
<option value={USER_ROLE_USER}>
|
||||
{ I18n.t(`site_settings.users.role_${USER_ROLE_USER}`) }
|
||||
</option>
|
||||
<option value={USER_ROLE_MODERATOR}>
|
||||
{ I18n.t(`site_settings.users.role_${USER_ROLE_MODERATOR}`) }
|
||||
</option>
|
||||
<option value={USER_ROLE_ADMIN}>
|
||||
{ I18n.t(`site_settings.users.role_${USER_ROLE_ADMIN}`) }
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<Button onClick={() => this._handleUpdateUserRole(selectedRole)} className="updateUserButton">
|
||||
{ I18n.t('common.buttons.update') }
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserForm;
|
||||
@@ -0,0 +1,103 @@
|
||||
import * as React from 'react';
|
||||
import I18n from 'i18n-js';
|
||||
|
||||
import UserEditable from './UserEditable';
|
||||
import Box from '../../common/Box';
|
||||
import SiteSettingsInfoBox from '../../common/SiteSettingsInfoBox';
|
||||
|
||||
import { UsersState } from '../../../reducers/usersReducer';
|
||||
import { UserRoles, USER_STATUS_ACTIVE, USER_STATUS_BLOCKED } from '../../../interfaces/IUser';
|
||||
import HttpStatus from '../../../constants/http_status';
|
||||
|
||||
interface Props {
|
||||
users: UsersState;
|
||||
settingsAreUpdating: boolean;
|
||||
settingsError: string;
|
||||
|
||||
requestUsers(): void;
|
||||
updateUserRole(
|
||||
id: number,
|
||||
role: UserRoles,
|
||||
authenticityToken: string,
|
||||
): Promise<any>;
|
||||
updateUserStatus(
|
||||
id: number,
|
||||
status: typeof USER_STATUS_ACTIVE | typeof USER_STATUS_BLOCKED,
|
||||
authenticityToken: string,
|
||||
): void;
|
||||
|
||||
currentUserEmail: string;
|
||||
currentUserRole: UserRoles;
|
||||
authenticityToken: string;
|
||||
}
|
||||
|
||||
class UsersSiteSettingsP extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._handleUpdateUserRole = this._handleUpdateUserRole.bind(this);
|
||||
this._handleUpdateUserStatus = this._handleUpdateUserStatus.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.requestUsers();
|
||||
}
|
||||
|
||||
_handleUpdateUserRole(id: number, role: UserRoles, closeEditMode: Function) {
|
||||
this.props.updateUserRole(
|
||||
id,
|
||||
role,
|
||||
this.props.authenticityToken,
|
||||
).then(res => {
|
||||
if (res?.status !== HttpStatus.OK) return;
|
||||
|
||||
closeEditMode();
|
||||
});
|
||||
}
|
||||
|
||||
_handleUpdateUserStatus(id: number, status: typeof USER_STATUS_ACTIVE | typeof USER_STATUS_BLOCKED) {
|
||||
this.props.updateUserStatus(
|
||||
id,
|
||||
status,
|
||||
this.props.authenticityToken,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
users,
|
||||
settingsAreUpdating,
|
||||
settingsError,
|
||||
currentUserRole,
|
||||
currentUserEmail,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box>
|
||||
<h2>{ I18n.t('site_settings.users.title') }</h2>
|
||||
|
||||
<ul className="usersList">
|
||||
{
|
||||
users.items.map((user, i) => (
|
||||
<UserEditable
|
||||
user={user}
|
||||
updateUserRole={this._handleUpdateUserRole}
|
||||
updateUserStatus={this._handleUpdateUserStatus}
|
||||
|
||||
currentUserEmail={currentUserEmail}
|
||||
currentUserRole={currentUserRole}
|
||||
key={i}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</Box>
|
||||
|
||||
<SiteSettingsInfoBox areUpdating={settingsAreUpdating || users.areLoading} error={settingsError} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UsersSiteSettingsP;
|
||||
39
app/javascript/components/SiteSettings/Users/index.tsx
Normal file
39
app/javascript/components/SiteSettings/Users/index.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import * as React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Store } from 'redux';
|
||||
|
||||
import UsersSiteSettings from '../../../containers/UsersSiteSettings';
|
||||
|
||||
import createStoreHelper from '../../../helpers/createStore';
|
||||
import { UserRoles } from '../../../interfaces/IUser';
|
||||
import { State } from '../../../reducers/rootReducer';
|
||||
|
||||
interface Props {
|
||||
currentUserEmail: string;
|
||||
currentUserRole: UserRoles;
|
||||
authenticityToken: string;
|
||||
}
|
||||
|
||||
class UsersSiteSettingsRoot extends React.Component<Props> {
|
||||
store: Store<State, any>;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.store = createStoreHelper();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Provider store={this.store}>
|
||||
<UsersSiteSettings
|
||||
currentUserEmail={this.props.currentUserEmail}
|
||||
currentUserRole={this.props.currentUserRole}
|
||||
authenticityToken={this.props.authenticityToken}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UsersSiteSettingsRoot;
|
||||
49
app/javascript/containers/UsersSiteSettings.tsx
Normal file
49
app/javascript/containers/UsersSiteSettings.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import UsersSiteSettingsP from "../components/SiteSettings/Users/UsersSiteSettingsP";
|
||||
|
||||
import { requestUsers } from "../actions/User/requestUsers";
|
||||
import { updateUser } from "../actions/User/updateUser";
|
||||
import { UserRoles, USER_STATUS_ACTIVE, USER_STATUS_BLOCKED } from "../interfaces/IUser";
|
||||
import { State } from "../reducers/rootReducer";
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
users: state.users,
|
||||
settingsAreUpdating: state.siteSettings.users.areUpdating,
|
||||
settingsError: state.siteSettings.users.error,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: any) => ({
|
||||
requestUsers() {
|
||||
dispatch(requestUsers());
|
||||
},
|
||||
|
||||
updateUserRole(
|
||||
id: number,
|
||||
role: UserRoles,
|
||||
authenticityToken: string,
|
||||
): Promise<any> {
|
||||
return dispatch(updateUser({
|
||||
id,
|
||||
role,
|
||||
authenticityToken,
|
||||
}));
|
||||
},
|
||||
|
||||
updateUserStatus(
|
||||
id: number,
|
||||
status: typeof USER_STATUS_ACTIVE | typeof USER_STATUS_BLOCKED,
|
||||
authenticityToken: string,
|
||||
) {
|
||||
dispatch(updateUser({
|
||||
id,
|
||||
status,
|
||||
authenticityToken,
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(UsersSiteSettingsP);
|
||||
29
app/javascript/interfaces/IUser.ts
Normal file
29
app/javascript/interfaces/IUser.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
// Roles
|
||||
export const USER_ROLE_USER = 'user';
|
||||
export const USER_ROLE_MODERATOR = 'moderator';
|
||||
export const USER_ROLE_ADMIN = 'admin';
|
||||
|
||||
export type UserRoles =
|
||||
typeof USER_ROLE_USER |
|
||||
typeof USER_ROLE_MODERATOR |
|
||||
typeof USER_ROLE_ADMIN;
|
||||
|
||||
// Statuses
|
||||
export const USER_STATUS_ACTIVE = 'active';
|
||||
export const USER_STATUS_BLOCKED = 'blocked';
|
||||
export const USER_STATUS_DELETED = 'deleted';
|
||||
|
||||
export type UserStatuses =
|
||||
typeof USER_STATUS_ACTIVE |
|
||||
typeof USER_STATUS_BLOCKED |
|
||||
typeof USER_STATUS_DELETED;
|
||||
|
||||
interface IUser {
|
||||
id: number;
|
||||
email: string;
|
||||
fullName: string;
|
||||
role: UserRoles;
|
||||
status: UserStatuses;
|
||||
}
|
||||
|
||||
export default IUser;
|
||||
9
app/javascript/interfaces/json/IUser.ts
Normal file
9
app/javascript/interfaces/json/IUser.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
interface IUserJSON {
|
||||
id: number;
|
||||
email: string;
|
||||
full_name: string;
|
||||
role: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export default IUserJSON;
|
||||
58
app/javascript/reducers/SiteSettings/usersReducer.ts
Normal file
58
app/javascript/reducers/SiteSettings/usersReducer.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
UsersRequestActionTypes,
|
||||
USERS_REQUEST_START,
|
||||
USERS_REQUEST_SUCCESS,
|
||||
USERS_REQUEST_FAILURE,
|
||||
} from '../../actions/User/requestUsers';
|
||||
|
||||
import {
|
||||
UserUpdateActionTypes,
|
||||
USER_UPDATE_START,
|
||||
USER_UPDATE_SUCCESS,
|
||||
USER_UPDATE_FAILURE,
|
||||
} from '../../actions/User/updateUser';
|
||||
|
||||
export interface SiteSettingsUsersState {
|
||||
areUpdating: boolean;
|
||||
error: string;
|
||||
}
|
||||
|
||||
const initialState: SiteSettingsUsersState = {
|
||||
areUpdating: false,
|
||||
error: '',
|
||||
};
|
||||
|
||||
const siteSettingsUsersReducer = (
|
||||
state = initialState,
|
||||
action: UsersRequestActionTypes | UserUpdateActionTypes,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case USERS_REQUEST_START:
|
||||
case USER_UPDATE_START:
|
||||
return {
|
||||
...state,
|
||||
areUpdating: true,
|
||||
};
|
||||
|
||||
case USERS_REQUEST_SUCCESS:
|
||||
case USER_UPDATE_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
areUpdating: false,
|
||||
error: '',
|
||||
};
|
||||
|
||||
case USERS_REQUEST_FAILURE:
|
||||
case USER_UPDATE_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
areUpdating: false,
|
||||
error: action.error,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default siteSettingsUsersReducer;
|
||||
@@ -38,7 +38,7 @@ const initialState: BoardsState = {
|
||||
items: [],
|
||||
areLoading: false,
|
||||
error: '',
|
||||
}
|
||||
};
|
||||
|
||||
const boardsReducer = (
|
||||
state = initialState,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { combineReducers } from 'redux';
|
||||
import postsReducer from './postsReducer';
|
||||
import boardsReducer from './boardsReducer';
|
||||
import postStatusesReducer from './postStatusesReducer';
|
||||
import usersReducer from './usersReducer';
|
||||
import currentPostReducer from './currentPostReducer';
|
||||
import siteSettingsReducer from './siteSettingsReducer';
|
||||
|
||||
@@ -10,6 +11,7 @@ const rootReducer = combineReducers({
|
||||
posts: postsReducer,
|
||||
boards: boardsReducer,
|
||||
postStatuses: postStatusesReducer,
|
||||
users: usersReducer,
|
||||
currentPost: currentPostReducer,
|
||||
siteSettings: siteSettingsReducer,
|
||||
});
|
||||
|
||||
@@ -61,20 +61,37 @@ import {
|
||||
POSTSTATUS_UPDATE_FAILURE,
|
||||
} from '../actions/PostStatus/updatePostStatus';
|
||||
|
||||
import {
|
||||
UsersRequestActionTypes,
|
||||
USERS_REQUEST_START,
|
||||
USERS_REQUEST_SUCCESS,
|
||||
USERS_REQUEST_FAILURE,
|
||||
} from '../actions/User/requestUsers';
|
||||
|
||||
import {
|
||||
UserUpdateActionTypes,
|
||||
USER_UPDATE_START,
|
||||
USER_UPDATE_SUCCESS,
|
||||
USER_UPDATE_FAILURE,
|
||||
} from '../actions/User/updateUser';
|
||||
|
||||
import siteSettingsBoardsReducer, { SiteSettingsBoardsState } from './SiteSettings/boardsReducer';
|
||||
import siteSettingsPostStatusesReducer, { SiteSettingsPostStatusesState } from './SiteSettings/postStatusesReducer';
|
||||
import siteSettingsRoadmapReducer, { SiteSettingsRoadmapState } from './SiteSettings/roadmapReducer';
|
||||
import siteSettingsUsersReducer, { SiteSettingsUsersState } from './SiteSettings/usersReducer';
|
||||
|
||||
interface SiteSettingsState {
|
||||
boards: SiteSettingsBoardsState;
|
||||
postStatuses: SiteSettingsPostStatusesState;
|
||||
roadmap: SiteSettingsRoadmapState;
|
||||
users: SiteSettingsUsersState;
|
||||
}
|
||||
|
||||
const initialState: SiteSettingsState = {
|
||||
boards: siteSettingsBoardsReducer(undefined, {} as any),
|
||||
postStatuses: siteSettingsPostStatusesReducer(undefined, {} as any),
|
||||
roadmap: siteSettingsRoadmapReducer(undefined, {} as any),
|
||||
users: siteSettingsUsersReducer(undefined, {} as any),
|
||||
};
|
||||
|
||||
const siteSettingsReducer = (
|
||||
@@ -88,7 +105,9 @@ const siteSettingsReducer = (
|
||||
PostStatusOrderUpdateActionTypes |
|
||||
PostStatusDeleteActionTypes |
|
||||
PostStatusSubmitActionTypes |
|
||||
PostStatusUpdateActionTypes
|
||||
PostStatusUpdateActionTypes |
|
||||
UsersRequestActionTypes |
|
||||
UserUpdateActionTypes
|
||||
): SiteSettingsState => {
|
||||
switch (action.type) {
|
||||
case BOARDS_REQUEST_START:
|
||||
@@ -134,6 +153,17 @@ const siteSettingsReducer = (
|
||||
roadmap: siteSettingsRoadmapReducer(state.roadmap, action),
|
||||
};
|
||||
|
||||
case USERS_REQUEST_START:
|
||||
case USERS_REQUEST_SUCCESS:
|
||||
case USERS_REQUEST_FAILURE:
|
||||
case USER_UPDATE_START:
|
||||
case USER_UPDATE_SUCCESS:
|
||||
case USER_UPDATE_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
users: siteSettingsUsersReducer(state.users, action),
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
75
app/javascript/reducers/usersReducer.ts
Normal file
75
app/javascript/reducers/usersReducer.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import {
|
||||
UsersRequestActionTypes,
|
||||
USERS_REQUEST_START,
|
||||
USERS_REQUEST_SUCCESS,
|
||||
USERS_REQUEST_FAILURE,
|
||||
} from '../actions/User/requestUsers';
|
||||
|
||||
import {
|
||||
UserUpdateActionTypes,
|
||||
USER_UPDATE_SUCCESS,
|
||||
} from '../actions/User/updateUser';
|
||||
|
||||
import IUser from "../interfaces/IUser";
|
||||
|
||||
export interface UsersState {
|
||||
items: Array<IUser>;
|
||||
areLoading: boolean;
|
||||
error: string;
|
||||
}
|
||||
|
||||
const initialState: UsersState = {
|
||||
items: [],
|
||||
areLoading: false,
|
||||
error: '',
|
||||
};
|
||||
|
||||
const usersReducer = (
|
||||
state = initialState,
|
||||
action: UsersRequestActionTypes | UserUpdateActionTypes,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case USERS_REQUEST_START:
|
||||
return {
|
||||
...state,
|
||||
areLoading: true,
|
||||
};
|
||||
|
||||
case USERS_REQUEST_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
areLoading: false,
|
||||
error: '',
|
||||
items: action.users.map(userJson => ({
|
||||
id: userJson.id,
|
||||
email: userJson.email,
|
||||
fullName: userJson.full_name,
|
||||
role: userJson.role,
|
||||
status: userJson.status,
|
||||
})),
|
||||
};
|
||||
|
||||
case USERS_REQUEST_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
areLoading: false,
|
||||
error: action.error,
|
||||
};
|
||||
|
||||
case USER_UPDATE_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
items: state.items.map(user => {
|
||||
return (user.id === action.user.id) ?
|
||||
{...user, role: action.user.role, status: action.user.status}
|
||||
:
|
||||
user;
|
||||
}),
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default usersReducer;
|
||||
@@ -172,4 +172,9 @@
|
||||
background-color: $primary-color !important;
|
||||
border-color: $primary-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
.selectPicker {
|
||||
@extend
|
||||
.custom-select;
|
||||
}
|
||||
@@ -119,12 +119,6 @@
|
||||
@extend
|
||||
.d-flex,
|
||||
.justify-content-between;
|
||||
|
||||
.selectPicker {
|
||||
@extend
|
||||
.custom-select,
|
||||
.mx-2;
|
||||
}
|
||||
}
|
||||
|
||||
.postDescription {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
ul.usersList {
|
||||
@extend
|
||||
.pl-1;
|
||||
|
||||
list-style: none;
|
||||
|
||||
li.userEditable {
|
||||
@extend
|
||||
.d-flex,
|
||||
.justify-content-between,
|
||||
.my-2,
|
||||
.p-3;
|
||||
|
||||
.userGravatar {
|
||||
@extend .mr-3, .align-self-center;
|
||||
}
|
||||
|
||||
.userFullName {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.userInfo {
|
||||
@extend .d-flex;
|
||||
|
||||
.userFullNameRoleStatus {
|
||||
@extend
|
||||
.d-flex,
|
||||
.flex-column;
|
||||
}
|
||||
}
|
||||
|
||||
.userEditableActions {
|
||||
@extend .align-self-center;
|
||||
}
|
||||
|
||||
.userForm {
|
||||
@extend .d-flex;
|
||||
|
||||
.userFullNameRoleForm {
|
||||
@extend .d-flex, .flex-column;
|
||||
}
|
||||
}
|
||||
|
||||
.updateUserButton { @extend .align-self-center; }
|
||||
|
||||
.userEditCancelButton { @extend .align-self-center; }
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover { text-decoration: underline; }
|
||||
}
|
||||
|
||||
a.actionDisabled {
|
||||
@extend .mutedText;
|
||||
|
||||
text-decoration: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.updateUserButton {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.userStatusblocked { color: orange; }
|
||||
.userStatusdeleted { color: red; }
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
@import 'components/SiteSettings/Boards';
|
||||
@import 'components/SiteSettings/PostStatuses';
|
||||
@import 'components/SiteSettings/Roadmap';
|
||||
@import 'components/SiteSettings/Users';
|
||||
|
||||
/* Icons */
|
||||
@import 'icons/drag_icon';
|
||||
@@ -8,7 +8,10 @@ class User < ApplicationRecord
|
||||
has_many :comments, dependent: :destroy
|
||||
|
||||
enum role: [:user, :moderator, :admin]
|
||||
enum status: [:active, :blocked, :deleted]
|
||||
|
||||
after_initialize :set_default_role, if: :new_record?
|
||||
after_initialize :set_default_status, if: :new_record?
|
||||
after_initialize :skip_confirmation, if: :new_record?
|
||||
|
||||
validates :full_name, presence: true, length: { in: 2..32 }
|
||||
@@ -17,6 +20,18 @@ class User < ApplicationRecord
|
||||
self.role ||= :user
|
||||
end
|
||||
|
||||
def set_default_status
|
||||
self.status ||= :active
|
||||
end
|
||||
|
||||
def active_for_authentication?
|
||||
super && active?
|
||||
end
|
||||
|
||||
def inactive_message
|
||||
active? ? super : :blocked_or_deleted
|
||||
end
|
||||
|
||||
def skip_confirmation
|
||||
return if Rails.application.email_confirmation?
|
||||
skip_confirmation!
|
||||
@@ -36,4 +51,20 @@ class User < ApplicationRecord
|
||||
def admin?
|
||||
role == 'admin'
|
||||
end
|
||||
|
||||
def moderator?
|
||||
role == 'moderator'
|
||||
end
|
||||
|
||||
def user?
|
||||
role == 'user'
|
||||
end
|
||||
|
||||
def active?
|
||||
status == 'active'
|
||||
end
|
||||
|
||||
def blocked?
|
||||
status == 'blocked'
|
||||
end
|
||||
end
|
||||
|
||||
25
app/policies/user_policy.rb
Normal file
25
app/policies/user_policy.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
class UserPolicy < ApplicationPolicy
|
||||
def permitted_attributes_for_update
|
||||
if user.admin?
|
||||
[:role, :status]
|
||||
elsif user.moderator?
|
||||
[:status]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def index?
|
||||
user.power_user?
|
||||
end
|
||||
|
||||
def update?
|
||||
if user.admin?
|
||||
record.id != user.id
|
||||
elsif user.moderator?
|
||||
record.user?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,20 +0,0 @@
|
||||
<%#
|
||||
# Flash Partial
|
||||
|
||||
This partial renders flash messages on every page.
|
||||
|
||||
## Relevant Helpers:
|
||||
|
||||
- `flash`:
|
||||
Returns a hash,
|
||||
where the keys are the type of flash (alert, error, notice, etc)
|
||||
and the values are the message to be displayed.
|
||||
%>
|
||||
|
||||
<% if flash.any? %>
|
||||
<div class="flashes">
|
||||
<% flash.each do |key, value| -%>
|
||||
<div class="flash flash-<%= key %>"><%= value.html_safe %></div>
|
||||
<% end -%>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -1,45 +0,0 @@
|
||||
<%#
|
||||
# Form Partial
|
||||
|
||||
This partial is rendered on a resource's `new` and `edit` pages,
|
||||
and renders all form fields for a resource's editable attributes.
|
||||
|
||||
## Local variables:
|
||||
|
||||
- `page`:
|
||||
An instance of [Administrate::Page::Form][1].
|
||||
Contains helper methods to display a form,
|
||||
and knows which attributes should be displayed in the resource's form.
|
||||
|
||||
[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Form
|
||||
%>
|
||||
|
||||
<%= form_for([namespace, page.resource], html: { class: "form" }) do |f| %>
|
||||
<% if page.resource.errors.any? %>
|
||||
<div id="error_explanation">
|
||||
<h2 style="text-align: center;">
|
||||
<%= t(
|
||||
"administrate.form.errors",
|
||||
pluralized_errors: pluralize(page.resource.errors.count, t("administrate.form.error")),
|
||||
resource_name: display_resource_name(page.resource_name)
|
||||
) %>
|
||||
</h2>
|
||||
|
||||
<ul>
|
||||
<% page.resource.errors.full_messages.each do |message| %>
|
||||
<li class="flash-error"><%= message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% page.attributes.each do |attribute| -%>
|
||||
<div class="field-unit field-unit--<%= attribute.html_class %>">
|
||||
<%= render_field attribute, f: f %>
|
||||
</div>
|
||||
<% end -%>
|
||||
|
||||
<div class="form-actions">
|
||||
<%= f.submit %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -1,13 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<symbol id="icon-cancel" viewBox="0 0 48 48">
|
||||
<path fill-rule="evenodd" d="M24 19.757l-8.485-8.485c-.784-.783-2.047-.782-2.827 0l-1.417 1.416c-.777.777-.78 2.046.002 2.827L19.757 24l-8.485 8.485c-.783.784-.782 2.047 0 2.827l1.416 1.417c.777.777 2.046.78 2.827-.002L24 28.243l8.485 8.485c.784.783 2.047.782 2.827 0l1.417-1.416c.777-.777.78-2.046-.002-2.827L28.243 24l8.485-8.485c.783-.784.782-2.047 0-2.827l-1.416-1.417c-.777-.777-2.046-.78-2.827.002L24 19.757zM24 47c12.703 0 23-10.297 23-23S36.703 1 24 1 1 11.297 1 24s10.297 23 23 23z" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="icon-eyeglass" viewBox="0 0 48 48">
|
||||
<path d="M27.885 32.515c-2.864 1.966-6.333 3.116-10.07 3.116C7.976 35.63 0 27.656 0 17.817 0 7.976 7.976 0 17.816 0S35.63 7.976 35.63 17.816c0 3.736-1.15 7.205-3.115 10.07l14.53 14.53c1.278 1.277 1.275 3.352 0 4.628-1.28 1.278-3.353 1.278-4.63 0l-14.53-14.53zm-10.07-3.736c6.056 0 10.964-4.91 10.964-10.964 0-6.055-4.91-10.964-10.964-10.964-6.055 0-10.964 4.91-10.964 10.964 0 6.055 4.91 10.963 10.964 10.963z" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="icon-up-caret" viewBox="0 0 48 48">
|
||||
<path d="M2.988 33.02c-1.66 0-1.943-.81-.618-1.824l20-15.28c.878-.672 2.31-.67 3.188 0l20.075 15.288c1.316 1.003 1.048 1.816-.62 1.816H2.987z" />
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,21 +0,0 @@
|
||||
<%#
|
||||
# Javascript Partial
|
||||
|
||||
This partial imports the necessary javascript on each page.
|
||||
By default, it includes the application JS,
|
||||
but each page can define additional JS sources
|
||||
by providing a `content_for(:javascript)` block.
|
||||
%>
|
||||
|
||||
<% Administrate::Engine.javascripts.each do |js_path| %>
|
||||
<%= javascript_include_tag js_path %>
|
||||
<% end %>
|
||||
|
||||
<%= yield :javascript %>
|
||||
|
||||
<% if Rails.env.test? %>
|
||||
<%= javascript_tag do %>
|
||||
$.fx.off = true;
|
||||
$.ajaxSetup({ async: false });
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -1,26 +0,0 @@
|
||||
<%#
|
||||
# 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
|
||||
%>
|
||||
|
||||
<nav class="navigation" role="navigation">
|
||||
<%= link_to(
|
||||
"⇦ Back to site",
|
||||
root_path,
|
||||
class: "navigation__link button",
|
||||
style: "color: #f6f7f7; background-color: #293f54; font-size: 15pt;",
|
||||
"data-turbolinks": "false"
|
||||
) %>
|
||||
|
||||
<% Administrate::Namespace.new(namespace).resources_with_index_route.each do |resource| %>
|
||||
<%= link_to(
|
||||
display_resource_name(resource),
|
||||
resource_index_route(resource),
|
||||
class: "navigation__link navigation__link--#{nav_link_state(resource)}"
|
||||
) if valid_action?(:index, resource) && show_action?(:index, model_from_resource(resource)) %>
|
||||
<% end %>
|
||||
</nav>
|
||||
@@ -1,14 +0,0 @@
|
||||
<%#
|
||||
# Stylesheet Partial
|
||||
|
||||
This partial imports the necessary stylesheets on each page.
|
||||
By default, it includes the application CSS,
|
||||
but each page can define additional CSS sources
|
||||
by providing a `content_for(:stylesheet)` block.
|
||||
%>
|
||||
|
||||
<% Administrate::Engine.stylesheets.each do |css_path| %>
|
||||
<%= stylesheet_link_tag css_path %>
|
||||
<% end %>
|
||||
|
||||
<%= yield :stylesheet %>
|
||||
@@ -1,6 +0,0 @@
|
||||
<div class="field-unit__label">
|
||||
<%= f.label field.attribute %>
|
||||
</div>
|
||||
<div class="field-unit__field">
|
||||
<%= f.color_field field.attribute, style: 'height: 40px;' %>
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
<div
|
||||
style="background-color: <%= field.to_s %>; width: 32px; height: 32px; border-radius: 32px;">
|
||||
</div>
|
||||
@@ -1,4 +0,0 @@
|
||||
<div
|
||||
style="background-color: <%= field.to_s %>; width: 32px; height: 32px; border-radius: 32px;">
|
||||
</div>
|
||||
(<%= field.to_s %>)
|
||||
@@ -1,14 +0,0 @@
|
||||
<div class="field-unit__label">
|
||||
<%= f.label field.attribute %>
|
||||
</div>
|
||||
<div class="field-unit__field">
|
||||
<%= f.select field.attribute, field.select_field_values(f) %>
|
||||
</div>
|
||||
|
||||
<div
|
||||
title="Click to learn more"
|
||||
style="margin-left: 8px; cursor: pointer;"
|
||||
onclick="alert('If you add a user with role \'admin\', it will have same powers as you!')"
|
||||
>
|
||||
⚠️
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<%= field.to_s.titleize %>
|
||||
@@ -1 +0,0 @@
|
||||
<%= field.to_s.titleize %>
|
||||
@@ -35,13 +35,15 @@
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<% if current_user.power_user? %>
|
||||
<%= link_to t('header.menu.site_settings'), site_settings_boards_path, class: 'dropdown-item' %>
|
||||
<%= link_to t('header.menu.admin_panel'), admin_root_path, class: 'dropdown-item', 'data-turbolinks': 'false' %>
|
||||
<%=
|
||||
link_to t('header.menu.site_settings'),
|
||||
current_user.admin? ? site_settings_boards_path : site_settings_users_path,
|
||||
class: 'dropdown-item'
|
||||
%>
|
||||
<div class="dropdown-divider"></div>
|
||||
<% end %>
|
||||
|
||||
<%= link_to t('header.menu.profile_settings'), edit_user_registration_path, class: 'dropdown-item' %>
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
<%= link_to t('header.menu.sign_out'), destroy_user_session_path, method: :delete, class: 'dropdown-item' %>
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<%#
|
||||
# Application Layout
|
||||
|
||||
This view template is used as the layout
|
||||
for every page that Administrate generates.
|
||||
|
||||
By default, it renders:
|
||||
- Navigation
|
||||
- Content for a search bar
|
||||
(if provided by a `content_for` block in a nested page)
|
||||
- Flashes
|
||||
- Links to stylesheets and JavaScripts
|
||||
%>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="<%= I18n.locale %>">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="ROBOTS" content="NOODP">
|
||||
<meta name="viewport" content="initial-scale=1">
|
||||
<title>
|
||||
<%= content_for(:title) %> - Admin Panel - <%= Rails.application.name %>
|
||||
</title>
|
||||
<%= render "stylesheet" %>
|
||||
<%= csrf_meta_tags %>
|
||||
</head>
|
||||
<body>
|
||||
<%= render "icons" %>
|
||||
|
||||
<div class="app-container">
|
||||
<%= render "navigation" -%>
|
||||
|
||||
<main class="main-content" role="main">
|
||||
<%= render "flashes" -%>
|
||||
<%= yield %>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<%= render "javascript" %>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,9 +3,13 @@
|
||||
<span class="boxTitleText"><%= t('site_settings.menu.title') %></span>
|
||||
|
||||
<div class="verticalNavigation" role="tablist" aria-orientation="vertical">
|
||||
<%= render 'menu_link', label: t('site_settings.menu.boards'), path: site_settings_boards_path %>
|
||||
<%= render 'menu_link', label: t('site_settings.menu.post_statuses'), path: site_settings_post_statuses_path %>
|
||||
<%= render 'menu_link', label: t('site_settings.menu.roadmap'), path: site_settings_roadmap_path %>
|
||||
<% if current_user.admin? %>
|
||||
<%= render 'menu_link', label: t('site_settings.menu.boards'), path: site_settings_boards_path %>
|
||||
<%= render 'menu_link', label: t('site_settings.menu.post_statuses'), path: site_settings_post_statuses_path %>
|
||||
<%= render 'menu_link', label: t('site_settings.menu.roadmap'), path: site_settings_roadmap_path %>
|
||||
<% end %>
|
||||
|
||||
<%= render 'menu_link', label: t('site_settings.menu.users'), path: site_settings_users_path %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
15
app/views/site_settings/users.html.erb
Normal file
15
app/views/site_settings/users.html.erb
Normal file
@@ -0,0 +1,15 @@
|
||||
<div class="twoColumnsContainer">
|
||||
<%= render 'menu' %>
|
||||
<div>
|
||||
<%=
|
||||
react_component(
|
||||
'SiteSettings/Users',
|
||||
{
|
||||
currentUserEmail: current_user.email,
|
||||
currentUserRole: current_user.role,
|
||||
authenticityToken: form_authenticity_token
|
||||
}
|
||||
)
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
@@ -16,6 +16,7 @@ en:
|
||||
timeout: "Your session expired. Please sign in again to continue."
|
||||
unauthenticated: "You need to sign in or sign up before continuing."
|
||||
unconfirmed: "You have to confirm your email address before continuing."
|
||||
blocked_or_deleted: "You cannot access your account because it has been blocked or deleted."
|
||||
mailer:
|
||||
confirmation_instructions:
|
||||
subject: "Confirmation instructions"
|
||||
|
||||
@@ -20,6 +20,7 @@ it:
|
||||
timeout: "La tua sessione è scaduta, accedi nuovamente per continuare."
|
||||
unauthenticated: "Devi accedere o registrarti per continuare."
|
||||
unconfirmed: "Devi confermare il tuo indirizzo email per continuare."
|
||||
blocked_or_deleted: "Non puoi accedere al tuo account perché è stato bloccato o eliminato."
|
||||
mailer:
|
||||
confirmation_instructions:
|
||||
subject: "Istruzioni per la conferma"
|
||||
|
||||
@@ -52,7 +52,6 @@ en:
|
||||
header:
|
||||
menu:
|
||||
site_settings: 'Site settings'
|
||||
admin_panel: 'Admin panel (deprecated)'
|
||||
profile_settings: 'Profile settings'
|
||||
sign_out: 'Sign out'
|
||||
log_in: 'Log in / Sign up'
|
||||
@@ -107,6 +106,7 @@ en:
|
||||
boards: 'Boards'
|
||||
post_statuses: 'Statuses'
|
||||
roadmap: 'Roadmap'
|
||||
users: 'Users'
|
||||
info_box:
|
||||
up_to_date: 'All changes saved'
|
||||
error: 'An error occurred: %{message}'
|
||||
@@ -128,6 +128,19 @@ en:
|
||||
title2: 'Not in roadmap'
|
||||
empty: 'The roadmap is empty.'
|
||||
help: 'You can add statuses to the roadmap by dragging them from the section below. If you instead want to create a new status or change their order, go to Site settings -> Statuses.'
|
||||
users:
|
||||
title: 'Users'
|
||||
block: 'Block'
|
||||
unblock: 'Unblock'
|
||||
block_confirmation: "%{name} won't be able to log in until it is unblocked. Are you sure?"
|
||||
unblock_confirmation: "%{name} will be able to log in and submit feedback. Are you sure?"
|
||||
role_to_admin_confirmation: "%{name} will have the same privileges as you, so they could even demote or block you. Proceed only if you really trust %{name}. Are you sure?"
|
||||
role_user: 'User'
|
||||
role_moderator: 'Moderator'
|
||||
role_admin: 'Administrator'
|
||||
status_active: 'Active'
|
||||
status_blocked: 'Blocked'
|
||||
status_deleted: 'Deleted'
|
||||
user_mailer:
|
||||
opening_greeting: 'Hello!'
|
||||
closing_greeting: 'Have a great day!'
|
||||
|
||||
@@ -52,7 +52,6 @@ it:
|
||||
header:
|
||||
menu:
|
||||
site_settings: 'Impostazioni sito'
|
||||
admin_panel: 'Admin panel (deprecato)'
|
||||
profile_settings: 'Impostazioni profilo'
|
||||
sign_out: 'Esci'
|
||||
log_in: 'Accedi / Registrati'
|
||||
@@ -107,6 +106,7 @@ it:
|
||||
boards: 'Bacheche'
|
||||
post_statuses: 'Stati'
|
||||
roadmap: 'Roadmap'
|
||||
users: 'Utenti'
|
||||
info_box:
|
||||
up_to_date: 'Tutte le modifiche sono state salvate'
|
||||
error: 'Si è verificato un errore: %{message}'
|
||||
@@ -128,6 +128,19 @@ it:
|
||||
title2: 'Non mostrati in roadmap'
|
||||
empty: 'La roadmap è vuota.'
|
||||
help: "Puoi aggiungere stati alla roadmap trascinandoli dalla sezione sottostante. Se invece vuoi creare un nuovo stato o cambiarne l'ordine, vai in Impostazioni sito -> Stati."
|
||||
users:
|
||||
title: 'Utenti'
|
||||
block: 'Blocca'
|
||||
unblock: 'Sblocca'
|
||||
block_confirmation: "%{name} non potrà effettuare l'accesso al sito finché non sarà sbloccato. Sei sicuro?"
|
||||
unblock_confirmation: "%{name} potrà effettuare l'accesso al sito e pubblicare contenuti. Sei sicuro?"
|
||||
role_to_admin_confirmation: "%{name} avrà i tuoi stessi privilegi, quindi potrebbe anche retrocederti a utente o bloccarti. Procedi solo se ti fidi di %{name}. Sei sicuro?"
|
||||
role_user: 'Utente'
|
||||
role_moderator: 'Moderatore'
|
||||
role_admin: 'Amministratore'
|
||||
status_active: 'Attivo'
|
||||
status_blocked: 'Bloccato'
|
||||
status_deleted: 'Eliminato'
|
||||
user_mailer:
|
||||
opening_greeting: 'Ciao!'
|
||||
closing_greeting: 'Buona giornata!'
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
Rails.application.routes.draw do
|
||||
root to: 'static_pages#roadmap'
|
||||
get '/roadmap', to: 'static_pages#roadmap'
|
||||
|
||||
namespace :admin do
|
||||
root to: 'boards#index'
|
||||
|
||||
resources :boards
|
||||
resources :comments
|
||||
resources :posts
|
||||
resources :post_statuses
|
||||
resources :users
|
||||
end
|
||||
|
||||
devise_for :users
|
||||
devise_for :users, :controllers => { :registrations => 'registrations' }
|
||||
resources :users, only: [:index, :update]
|
||||
|
||||
resources :posts, only: [:index, :create, :show, :update, :destroy] do
|
||||
resource :follows, only: [:create, :destroy]
|
||||
@@ -36,5 +27,6 @@ Rails.application.routes.draw do
|
||||
get 'boards'
|
||||
get 'post_statuses'
|
||||
get 'roadmap'
|
||||
get 'users'
|
||||
end
|
||||
end
|
||||
|
||||
5
db/migrate/20220622092039_add_status_to_users.rb
Normal file
5
db/migrate/20220622092039_add_status_to_users.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class AddStatusToUsers < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_column :users, :status, :integer
|
||||
end
|
||||
end
|
||||
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2022_05_21_161950) do
|
||||
ActiveRecord::Schema.define(version: 2022_06_22_092039) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@@ -106,6 +106,7 @@ ActiveRecord::Schema.define(version: 2022_05_21_161950) do
|
||||
t.integer "role"
|
||||
t.string "full_name"
|
||||
t.boolean "notifications_enabled", default: true, null: false
|
||||
t.integer "status"
|
||||
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
|
||||
t.index ["email"], name: "index_users_on_email", unique: true
|
||||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||
|
||||
@@ -22,4 +22,20 @@ FactoryBot.define do
|
||||
password { 'password' }
|
||||
role { 'admin' }
|
||||
end
|
||||
|
||||
factory :blocked, class: User do
|
||||
sequence(:email) { |n| "admin#{n}@example.com" }
|
||||
|
||||
full_name { 'First Last' }
|
||||
password { 'password' }
|
||||
status { 'blocked' }
|
||||
end
|
||||
|
||||
factory :deleted, class: User do
|
||||
sequence(:email) { |n| "admin#{n}@example.com" }
|
||||
|
||||
full_name { 'First Last' }
|
||||
password { 'password' }
|
||||
status { 'deleted' }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,7 +9,7 @@ RSpec.describe User, type: :model do
|
||||
expect(user).to be_valid
|
||||
end
|
||||
|
||||
it 'creates a user with role "user" by default' do
|
||||
it 'has role "user" by default' do
|
||||
expect(User.new.role).to eq('user')
|
||||
end
|
||||
|
||||
@@ -23,6 +23,24 @@ RSpec.describe User, type: :model do
|
||||
expect(admin).to be_valid
|
||||
end
|
||||
|
||||
it 'has status "active" by default' do
|
||||
expect(User.new.status).to eq('active')
|
||||
end
|
||||
|
||||
it 'can have the following statuses: "active", "blocked" and "deleted"' do
|
||||
active = user
|
||||
blocked = FactoryBot.build(:blocked)
|
||||
deleted = FactoryBot.build(:deleted)
|
||||
|
||||
expect(user.status).to eq('active')
|
||||
expect(blocked.status).to eq('blocked')
|
||||
expect(deleted.status).to eq('deleted')
|
||||
|
||||
expect(user).to be_valid
|
||||
expect(blocked).to be_valid
|
||||
expect(deleted).to be_valid
|
||||
end
|
||||
|
||||
it 'has a non-nil and non-empty full name' do
|
||||
nil_name_user = FactoryBot.build(:user, full_name: nil)
|
||||
empty_name_user = FactoryBot.build(:user, full_name: '')
|
||||
@@ -54,4 +72,11 @@ RSpec.describe User, type: :model do
|
||||
expect(moderator).to be_a_power_user
|
||||
expect(admin).to be_a_power_user
|
||||
end
|
||||
|
||||
it 'knows if it is active or blocked' do
|
||||
expect(user).to be_active
|
||||
|
||||
blocked = FactoryBot.build(:blocked)
|
||||
expect(blocked).to be_blocked
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'requests to boards in the admin panel', :admin_panel, type: :request do
|
||||
let(:user) { FactoryBot.create(:user) }
|
||||
let(:moderator) { FactoryBot.create(:moderator) }
|
||||
let(:admin) { FactoryBot.create(:admin) }
|
||||
|
||||
let(:board) { FactoryBot.create(:board) }
|
||||
|
||||
context 'when user is not logged in' do
|
||||
it 'redirects index action' do
|
||||
get admin_boards_path
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects show action' do
|
||||
get admin_board_path(board)
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects new action' do
|
||||
get new_admin_board_path
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects edit action' do
|
||||
get edit_admin_board_path(board)
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects create action' do
|
||||
post admin_boards_path, params: { board: { name: board.name + 'a' } }
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects update action' do
|
||||
patch admin_board_path(board), params: { board: { name: board.name + 'a' } }
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects destroy action' do
|
||||
delete admin_board_path(board)
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has role "user"' do
|
||||
before(:each) do
|
||||
user.confirm
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it 'redirects index action' do
|
||||
get admin_boards_path
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects show action' do
|
||||
get admin_board_path(board)
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects new action' do
|
||||
get new_admin_board_path
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects edit action' do
|
||||
get edit_admin_board_path(board)
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects create action' do
|
||||
post admin_boards_path, params: { board: { name: board.name + 'a' } }
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects update action' do
|
||||
patch admin_board_path(board), params: { board: { name: board.name + 'a' } }
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects destroy action' do
|
||||
delete admin_board_path(board)
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has role "moderator"' do
|
||||
before(:each) do
|
||||
moderator.confirm
|
||||
sign_in moderator
|
||||
end
|
||||
|
||||
it 'fulfills index action' do
|
||||
get admin_boards_path
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills show action' do
|
||||
get admin_board_path(board)
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills new action' do
|
||||
get new_admin_board_path
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills edit action' do
|
||||
get edit_admin_board_path(board)
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills create action' do
|
||||
post admin_boards_path, params: { board: { name: board.name + 'a' } }
|
||||
expect(response).to redirect_to(admin_board_path(board.id + 1))
|
||||
end
|
||||
it 'fulfills update action' do
|
||||
patch admin_board_path(board), params: { board: { name: board.name + 'a' } }
|
||||
expect(response).to redirect_to(admin_board_path(board))
|
||||
end
|
||||
it 'fulfills destroy action' do
|
||||
delete admin_board_path(board)
|
||||
expect(response).to redirect_to(admin_root_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has role "admin"' do
|
||||
before(:each) do
|
||||
admin.confirm
|
||||
sign_in admin
|
||||
end
|
||||
|
||||
it 'fulfills index action' do
|
||||
get admin_boards_path
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills show action' do
|
||||
get admin_board_path(board)
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills new action' do
|
||||
get new_admin_board_path
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills edit action' do
|
||||
get edit_admin_board_path(board)
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills create action' do
|
||||
post admin_boards_path, params: { board: { name: board.name + 'a' } }
|
||||
expect(response).to redirect_to(admin_board_path(board.id + 1))
|
||||
end
|
||||
it 'fulfills update action' do
|
||||
patch admin_board_path(board), params: { board: { name: board.name + 'a' } }
|
||||
expect(response).to redirect_to(admin_board_path(board))
|
||||
end
|
||||
it 'fulfills destroy action' do
|
||||
delete admin_board_path(board)
|
||||
expect(response).to redirect_to(admin_root_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,148 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'requests to post statuses in the admin panel', :admin_panel, type: :request do
|
||||
let(:user) { FactoryBot.create(:user) }
|
||||
let(:moderator) { FactoryBot.create(:moderator) }
|
||||
let(:admin) { FactoryBot.create(:admin) }
|
||||
|
||||
let(:post_status) { FactoryBot.create(:post_status) }
|
||||
|
||||
context 'when user is not logged in' do
|
||||
it 'redirects index action' do
|
||||
get admin_post_statuses_path
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects show action' do
|
||||
get admin_post_status_path(post_status)
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects new action' do
|
||||
get new_admin_post_status_path
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects edit action' do
|
||||
get edit_admin_post_status_path(post_status)
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects create action' do
|
||||
post admin_post_statuses_path, params: { post_status: { name: post_status.name + 'a' } }
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects update action' do
|
||||
patch admin_post_status_path(post_status), params: { post_status: { name: post_status.name + 'a' } }
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects destroy action' do
|
||||
delete admin_post_status_path(post_status)
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has role "user"' do
|
||||
before(:each) do
|
||||
user.confirm
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it 'redirects index action' do
|
||||
get admin_post_statuses_path
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects show action' do
|
||||
get admin_post_status_path(post_status)
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects new action' do
|
||||
get new_admin_post_status_path
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects edit action' do
|
||||
get edit_admin_post_status_path(post_status)
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects create action' do
|
||||
post admin_post_statuses_path, params: { post_status: { name: post_status.name + 'a' } }
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects update action' do
|
||||
patch admin_post_status_path(post_status), params: { post_status: { name: post_status.name + 'a' } }
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects destroy action' do
|
||||
delete admin_post_status_path(post_status)
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has role "moderator"' do
|
||||
before(:each) do
|
||||
moderator.confirm
|
||||
sign_in moderator
|
||||
end
|
||||
|
||||
it 'fulfills index action' do
|
||||
get admin_post_statuses_path
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills show action' do
|
||||
get admin_post_status_path(post_status)
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills new action' do
|
||||
get new_admin_post_status_path
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills edit action' do
|
||||
get edit_admin_post_status_path(post_status)
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills create action' do
|
||||
post admin_post_statuses_path, params: { post_status: { name: post_status.name + 'a', color: post_status.color } }
|
||||
expect(response).to redirect_to(admin_post_status_path(post_status.id + 1))
|
||||
end
|
||||
it 'fulfills update action' do
|
||||
patch admin_post_status_path(post_status), params: { post_status: { name: post_status.name + 'a' } }
|
||||
expect(response).to redirect_to(admin_post_status_path(post_status))
|
||||
end
|
||||
it 'fulfills destroy action' do
|
||||
delete admin_post_status_path(post_status)
|
||||
expect(response).to redirect_to(admin_post_statuses_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has role "admin"' do
|
||||
before(:each) do
|
||||
admin.confirm
|
||||
sign_in admin
|
||||
end
|
||||
|
||||
it 'fulfills index action' do
|
||||
get admin_post_statuses_path
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills show action' do
|
||||
get admin_post_status_path(post_status)
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills new action' do
|
||||
get new_admin_post_status_path
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills edit action' do
|
||||
get edit_admin_post_status_path(post_status)
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills create action' do
|
||||
post admin_post_statuses_path, params: { post_status: { name: post_status.name + 'a', color: post_status.color } }
|
||||
expect(response).to redirect_to(admin_post_status_path(post_status.id + 1))
|
||||
end
|
||||
it 'fulfills update action' do
|
||||
patch admin_post_status_path(post_status), params: { post_status: { name: post_status.name + 'a' } }
|
||||
expect(response).to redirect_to(admin_post_status_path(post_status))
|
||||
end
|
||||
it 'fulfills destroy action' do
|
||||
delete admin_post_status_path(post_status)
|
||||
expect(response).to redirect_to(admin_post_statuses_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,146 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'requests to users in the admin panel', :admin_panel, type: :request do
|
||||
let(:user) { FactoryBot.create(:user) }
|
||||
let(:moderator) { FactoryBot.create(:moderator) }
|
||||
let(:admin) { FactoryBot.create(:admin) }
|
||||
|
||||
context 'when user is not logged in' do
|
||||
it 'redirects index action' do
|
||||
get admin_users_path
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects show action' do
|
||||
get admin_user_path(user)
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects new action' do
|
||||
get new_admin_user_path
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects edit action' do
|
||||
get edit_admin_user_path(user)
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects create action' do
|
||||
post admin_users_path, params: { user: { full_name: user.full_name, email: user.email + 'a', password: user.password } }
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects update action' do
|
||||
patch admin_user_path(user), params: { user: { full_name: user.full_name } }
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
it 'redirects destroy action' do
|
||||
delete admin_user_path(user)
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has role "user"' do
|
||||
before(:each) do
|
||||
user.confirm
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it 'redirects index action' do
|
||||
get admin_users_path
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects show action' do
|
||||
get admin_user_path(user)
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects new action' do
|
||||
get new_admin_user_path
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects edit action' do
|
||||
get edit_admin_user_path(user)
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects create action' do
|
||||
post admin_users_path, params: { user: { full_name: user.full_name, email: user.email + 'a', password: user.password } }
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects update action' do
|
||||
patch admin_user_path(user), params: { user: { full_name: user.full_name } }
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects destroy action' do
|
||||
delete admin_user_path(user)
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has role "moderator"' do
|
||||
before(:each) do
|
||||
moderator.confirm
|
||||
sign_in moderator
|
||||
end
|
||||
|
||||
it 'redirects index action' do
|
||||
get admin_users_path
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects show action' do
|
||||
get admin_user_path(user)
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects new action' do
|
||||
get new_admin_user_path
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects edit action' do
|
||||
get edit_admin_user_path(user)
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects create action' do
|
||||
post admin_users_path, params: { user: { full_name: user.full_name, email: user.email + 'a', password: user.password } }
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects update action' do
|
||||
patch admin_user_path(user), params: { user: { full_name: user.full_name } }
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
it 'redirects destroy action' do
|
||||
delete admin_user_path(user)
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has role "admin"' do
|
||||
before(:each) do
|
||||
admin.confirm
|
||||
sign_in admin
|
||||
end
|
||||
|
||||
it 'fulfills index action' do
|
||||
get admin_users_path
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills show action' do
|
||||
get admin_user_path(user)
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills new action' do
|
||||
get new_admin_user_path
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills edit action' do
|
||||
get edit_admin_user_path(user)
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
it 'fulfills create action' do
|
||||
post admin_users_path, params: { user: { full_name: user.full_name, email: user.email + 'a', password: user.password } }
|
||||
expect(response).to redirect_to(admin_user_path(user.id + 1))
|
||||
end
|
||||
it 'fulfills update action' do
|
||||
patch admin_user_path(user), params: { user: { full_name: user.full_name + 'a', password: '' } }
|
||||
expect(response).to redirect_to(admin_user_path(user))
|
||||
end
|
||||
it 'fulfills destroy action' do
|
||||
delete admin_user_path(user)
|
||||
expect(response).to redirect_to(admin_users_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,128 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'admin panel routing', :aggregate_failures, type: :routing do
|
||||
it 'routes root to boards index action' do
|
||||
expect(get: '/admin').to route_to(
|
||||
controller: 'admin/boards', action: 'index')
|
||||
end
|
||||
|
||||
it 'routes boards' do
|
||||
expect(get: '/admin/boards').to route_to(
|
||||
controller: 'admin/boards', action: 'index'
|
||||
)
|
||||
expect(get: '/admin/boards/1').to route_to(
|
||||
controller: 'admin/boards', action: 'show', id: '1'
|
||||
)
|
||||
expect(get: '/admin/boards/new').to route_to(
|
||||
controller: 'admin/boards', action: 'new'
|
||||
)
|
||||
expect(get: '/admin/boards/1/edit').to route_to(
|
||||
controller: 'admin/boards', action: 'edit', id: '1'
|
||||
)
|
||||
expect(post: '/admin/boards').to route_to(
|
||||
controller: 'admin/boards', action: 'create'
|
||||
)
|
||||
expect(patch: '/admin/boards/1').to route_to(
|
||||
controller: 'admin/boards', action: 'update', id: '1'
|
||||
)
|
||||
expect(delete: '/admin/boards/1').to route_to(
|
||||
controller: 'admin/boards', action: 'destroy', id: '1'
|
||||
)
|
||||
end
|
||||
|
||||
it 'routes comments' do
|
||||
expect(get: '/admin/comments').to route_to(
|
||||
controller: 'admin/comments', action: 'index'
|
||||
)
|
||||
expect(get: '/admin/comments/1').to route_to(
|
||||
controller: 'admin/comments', action: 'show', id: '1'
|
||||
)
|
||||
expect(get: '/admin/comments/new').to route_to(
|
||||
controller: 'admin/comments', action: 'new'
|
||||
)
|
||||
expect(get: '/admin/comments/1/edit').to route_to(
|
||||
controller: 'admin/comments', action: 'edit', id: '1'
|
||||
)
|
||||
expect(post: '/admin/comments').to route_to(
|
||||
controller: 'admin/comments', action: 'create'
|
||||
)
|
||||
expect(patch: '/admin/comments/1').to route_to(
|
||||
controller: 'admin/comments', action: 'update', id: '1'
|
||||
)
|
||||
expect(delete: '/admin/comments/1').to route_to(
|
||||
controller: 'admin/comments', action: 'destroy', id: '1'
|
||||
)
|
||||
end
|
||||
|
||||
it 'routes posts' do
|
||||
expect(get: '/admin/posts').to route_to(
|
||||
controller: 'admin/posts', action: 'index'
|
||||
)
|
||||
expect(get: '/admin/posts/1').to route_to(
|
||||
controller: 'admin/posts', action: 'show', id: '1'
|
||||
)
|
||||
expect(get: '/admin/posts/new').to route_to(
|
||||
controller: 'admin/posts', action: 'new'
|
||||
)
|
||||
expect(get: '/admin/posts/1/edit').to route_to(
|
||||
controller: 'admin/posts', action: 'edit', id: '1'
|
||||
)
|
||||
expect(post: '/admin/posts').to route_to(
|
||||
controller: 'admin/posts', action: 'create'
|
||||
)
|
||||
expect(patch: '/admin/posts/1').to route_to(
|
||||
controller: 'admin/posts', action: 'update', id: '1'
|
||||
)
|
||||
expect(delete: '/admin/posts/1').to route_to(
|
||||
controller: 'admin/posts', action: 'destroy', id: '1'
|
||||
)
|
||||
end
|
||||
|
||||
it 'routes post statuses' do
|
||||
expect(get: '/admin/post_statuses').to route_to(
|
||||
controller: 'admin/post_statuses', action: 'index'
|
||||
)
|
||||
expect(get: '/admin/post_statuses/1').to route_to(
|
||||
controller: 'admin/post_statuses', action: 'show', id: '1'
|
||||
)
|
||||
expect(get: '/admin/post_statuses/new').to route_to(
|
||||
controller: 'admin/post_statuses', action: 'new'
|
||||
)
|
||||
expect(get: '/admin/post_statuses/1/edit').to route_to(
|
||||
controller: 'admin/post_statuses', action: 'edit', id: '1'
|
||||
)
|
||||
expect(post: '/admin/post_statuses').to route_to(
|
||||
controller: 'admin/post_statuses', action: 'create'
|
||||
)
|
||||
expect(patch: '/admin/post_statuses/1').to route_to(
|
||||
controller: 'admin/post_statuses', action: 'update', id: '1'
|
||||
)
|
||||
expect(delete: '/admin/post_statuses/1').to route_to(
|
||||
controller: 'admin/post_statuses', action: 'destroy', id: '1'
|
||||
)
|
||||
end
|
||||
|
||||
it 'routes users' do
|
||||
expect(get: '/admin/users').to route_to(
|
||||
controller: 'admin/users', action: 'index'
|
||||
)
|
||||
expect(get: '/admin/users/1').to route_to(
|
||||
controller: 'admin/users', action: 'show', id: '1'
|
||||
)
|
||||
expect(get: '/admin/users/new').to route_to(
|
||||
controller: 'admin/users', action: 'new'
|
||||
)
|
||||
expect(get: '/admin/users/1/edit').to route_to(
|
||||
controller: 'admin/users', action: 'edit', id: '1'
|
||||
)
|
||||
expect(post: '/admin/users').to route_to(
|
||||
controller: 'admin/users', action: 'create'
|
||||
)
|
||||
expect(patch: '/admin/users/1').to route_to(
|
||||
controller: 'admin/users', action: 'update', id: '1'
|
||||
)
|
||||
expect(delete: '/admin/users/1').to route_to(
|
||||
controller: 'admin/users', action: 'destroy', id: '1'
|
||||
)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user