Improve rails controllers (#118)

This commit is contained in:
Riccardo Graziosi
2022-06-10 12:03:33 +02:00
committed by GitHub
parent 8e75a85873
commit 94f77517a8
19 changed files with 244 additions and 66 deletions

View File

@@ -4,7 +4,6 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.6.6' ruby '2.6.6'
gem 'rails', '6.0.4.7' gem 'rails', '6.0.4.7'
gem 'i18n-js'
gem 'pg', '>= 0.18', '< 2.0' gem 'pg', '>= 0.18', '< 2.0'
@@ -23,6 +22,12 @@ gem 'bootsnap', '>= 1.4.2', require: false
# Authentication # Authentication
gem 'devise', '4.7.3' gem 'devise', '4.7.3'
# Authorization
gem 'pundit', '2.2.0'
# I18n (forward locales to JS)
gem 'i18n-js'
# Administration panel # Administration panel
gem "administrate", '0.16.0' gem "administrate", '0.16.0'

View File

@@ -146,18 +146,22 @@ GEM
matrix (0.4.2) matrix (0.4.2)
method_source (1.0.0) method_source (1.0.0)
mini_mime (1.1.2) mini_mime (1.1.2)
mini_portile2 (2.8.0)
minitest (5.15.0) minitest (5.15.0)
momentjs-rails (2.29.1.1) momentjs-rails (2.29.1.1)
railties (>= 3.1) railties (>= 3.1)
msgpack (1.5.1) msgpack (1.5.1)
nio4r (2.5.8) nio4r (2.5.8)
nokogiri (1.13.3-x86_64-linux) nokogiri (1.13.3)
mini_portile2 (~> 2.8.0)
racc (~> 1.4) racc (~> 1.4)
orm_adapter (0.5.0) orm_adapter (0.5.0)
pg (1.3.5) pg (1.3.5)
public_suffix (4.0.6) public_suffix (4.0.6)
puma (4.3.12) puma (4.3.12)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.2.0)
activesupport (>= 3.0.0)
racc (1.6.0) racc (1.6.0)
rack (2.2.3) rack (2.2.3)
rack-proxy (0.7.2) rack-proxy (0.7.2)
@@ -235,7 +239,7 @@ GEM
sprockets (>= 2.8, < 4.0) sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0) sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3) tilt (>= 1.1, < 3)
sassc (2.1.0-x86_64-linux) sassc (2.1.0)
ffi (~> 1.9) ffi (~> 1.9)
sassc-rails (2.1.2) sassc-rails (2.1.2)
railties (>= 4.0.0) railties (>= 4.0.0)
@@ -305,6 +309,7 @@ DEPENDENCIES
listen (>= 3.0.5, < 3.2) listen (>= 3.0.5, < 3.2)
pg (>= 0.18, < 2.0) pg (>= 0.18, < 2.0)
puma (~> 4.3) puma (~> 4.3)
pundit (= 2.2.0)
rails (= 6.0.4.7) rails (= 6.0.4.7)
react-rails (~> 2.6.0) react-rails (~> 2.6.0)
rspec-rails (~> 3.8.2) rspec-rails (~> 3.8.2)

View File

@@ -1,4 +1,8 @@
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
include Pundit::Authorization
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
before_action :configure_permitted_parameters, if: :devise_controller? before_action :configure_permitted_parameters, if: :devise_controller?
before_action :load_boards before_action :load_boards
@@ -12,4 +16,12 @@ class ApplicationController < ActionController::Base
def load_boards def load_boards
@boards = Board.select(:id, :name).order(order: :asc) @boards = Board.select(:id, :name).order(order: :asc)
end end
private
def user_not_authorized
render json: {
error: t('backend.errors.unauthorized')
}, status: :unauthorized
end
end end

View File

@@ -1,7 +1,7 @@
class BoardsController < ApplicationController class BoardsController < ApplicationController
include ApplicationHelper include ApplicationHelper
before_action :authenticate_admin, only: [:create, :update, :update_order, :destroy] before_action :authenticate_user!, only: [:create, :update, :update_order, :destroy]
def index def index
boards = Board.order(order: :asc) boards = Board.order(order: :asc)
@@ -15,6 +15,7 @@ class BoardsController < ApplicationController
def create def create
board = Board.new(board_params) board = Board.new(board_params)
authorize board
if board.save if board.save
render json: board, status: :created render json: board, status: :created
@@ -27,13 +28,12 @@ class BoardsController < ApplicationController
def update def update
board = Board.find(params[:id]) board = Board.find(params[:id])
authorize board
board.assign_attributes(board_params) board.assign_attributes(board_params)
if board.save if board.save
render json: board, status: :ok render json: board, status: :ok
else else
print board.errors.full_messages
render json: { render json: {
error: board.errors.full_messages error: board.errors.full_messages
}, status: :unprocessable_entity }, status: :unprocessable_entity
@@ -42,6 +42,7 @@ class BoardsController < ApplicationController
def destroy def destroy
board = Board.find(params[:id]) board = Board.find(params[:id])
authorize board
if board.destroy if board.destroy
render json: { render json: {
@@ -55,6 +56,8 @@ class BoardsController < ApplicationController
end end
def update_order def update_order
authorize Board
workflow_output = ReorderWorkflow.new( workflow_output = ReorderWorkflow.new(
entity_classname: Board, entity_classname: Board,
column_name: 'order', column_name: 'order',

View File

@@ -23,10 +23,10 @@ class CommentsController < ApplicationController
comment = Comment.new(comment_params) comment = Comment.new(comment_params)
if comment.save if comment.save
send_notifications(comment) SendNotificationForCommentWorkflow.new(comment: comment).run
render json: comment.attributes.merge( render json: comment.attributes.merge(
{ user_full_name: current_user.full_name, user_email: current_user.email} { user_full_name: current_user.full_name, user_email: current_user.email }
), status: :created ), status: :created
else else
render json: { render json: {
@@ -37,17 +37,12 @@ class CommentsController < ApplicationController
def update def update
comment = Comment.find(params[:id]) comment = Comment.find(params[:id])
authorize comment
comment.assign_attributes(comment_params) comment.assign_attributes(comment_params)
if !current_user.power_user? && current_user.id != post.user_id
render json: t('backend.errors.unauthorized'), status: :unauthorized
return
end
if comment.save if comment.save
render json: comment.attributes.merge( render json: comment.attributes.merge(
{ user_full_name: current_user.full_name, user_email: current_user.email} { user_full_name: current_user.full_name, user_email: current_user.email }
) )
else else
render json: { render json: {
@@ -57,41 +52,14 @@ class CommentsController < ApplicationController
end end
private private
def comment_params
params
.require(:comment)
.permit(:body, :parent_id, :is_post_update)
.merge(
user_id: current_user.id,
post_id: params[:post_id]
)
end
def send_notifications(comment) def comment_params
if comment.is_post_update # Post update params
UserMailer.notify_followers_of_post_update(comment: comment).deliver_later .require(:comment)
return .permit(:body, :parent_id, :is_post_update)
.merge(
user_id: current_user.id,
post_id: params[:post_id]
)
end end
if comment.parent_id == nil # Reply to a post
user = comment.post.user
if comment.user.id != user.id and
user.notifications_enabled? and
comment.post.follows.exists?(user_id: user.id)
UserMailer.notify_post_owner(comment: comment).deliver_later
end
else # Reply to a comment
parent_comment = comment.parent
user = parent_comment.user
if user.notifications_enabled? and
parent_comment.user.id != comment.user.id
UserMailer.notify_comment_owner(comment: comment).deliver_later
end
end
end
end end

View File

@@ -1,7 +1,7 @@
class PostStatusesController < ApplicationController class PostStatusesController < ApplicationController
include ApplicationHelper include ApplicationHelper
before_action :authenticate_admin, only: [:create, :update, :update_order, :destroy] before_action :authenticate_user!, only: [:create, :update, :update_order, :destroy]
def index def index
post_statuses = PostStatus.order(order: :asc) post_statuses = PostStatus.order(order: :asc)
@@ -11,6 +11,7 @@ class PostStatusesController < ApplicationController
def create def create
post_status = PostStatus.new(post_status_params) post_status = PostStatus.new(post_status_params)
authorize post_status
if post_status.save if post_status.save
render json: post_status, status: :created render json: post_status, status: :created
@@ -23,6 +24,7 @@ class PostStatusesController < ApplicationController
def update def update
post_status = PostStatus.find(params[:id]) post_status = PostStatus.find(params[:id])
authorize post_status
post_status.assign_attributes(post_status_params) post_status.assign_attributes(post_status_params)
if post_status.save if post_status.save
@@ -36,6 +38,7 @@ class PostStatusesController < ApplicationController
def destroy def destroy
post_status = PostStatus.find(params[:id]) post_status = PostStatus.find(params[:id])
authorize post_status
if post_status.destroy if post_status.destroy
render json: { render json: {
@@ -49,6 +52,8 @@ class PostStatusesController < ApplicationController
end end
def update_order def update_order
authorize PostStatus
workflow_output = ReorderWorkflow.new( workflow_output = ReorderWorkflow.new(
entity_classname: PostStatus, entity_classname: PostStatus,
column_name: 'order', column_name: 'order',

View File

@@ -52,11 +52,7 @@ class PostsController < ApplicationController
def update def update
post = Post.find(params[:id]) post = Post.find(params[:id])
authorize post
if !current_user.power_user? && current_user.id != post.user_id
render json: t('backend.errors.unauthorized'), status: :unauthorized
return
end
post.board_id = params[:post][:board_id] if params[:post].has_key?(:board_id) post.board_id = params[:post][:board_id] if params[:post].has_key?(:board_id)
@@ -91,7 +87,7 @@ class PostsController < ApplicationController
private private
def filter_params def filter_params
defaults = { board_id: Board.first.id } defaults = Board.first ? { board_id: Board.first.id } : {}
params params
.permit(:board_id, :post_status_id, :page, :search) .permit(:board_id, :post_status_id, :page, :search)

View File

@@ -1,2 +0,0 @@
module BoardsHelper
end

View File

@@ -1,2 +0,0 @@
module CommentsHelper
end

View File

@@ -1,2 +0,0 @@
module LikesHelper
end

View File

@@ -1,2 +0,0 @@
module PostsHelper
end

View File

@@ -0,0 +1,53 @@
# frozen_string_literal: true
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
def index?
false
end
def show?
false
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
class Scope
def initialize(user, scope)
@user = user
@scope = scope
end
def resolve
raise NotImplementedError, "You must define #resolve in #{self.class}"
end
private
attr_reader :user, :scope
end
end

View File

@@ -0,0 +1,17 @@
class BoardPolicy < ApplicationPolicy
def create?
user.admin?
end
def update?
user.admin?
end
def destroy?
user.admin?
end
def update_order?
user.admin?
end
end

View File

@@ -0,0 +1,5 @@
class CommentPolicy < ApplicationPolicy
def update?
user == record.user or user.power_user?
end
end

View File

@@ -0,0 +1,5 @@
class PostPolicy < ApplicationPolicy
def update?
user == record.user or user.power_user?
end
end

View File

@@ -0,0 +1,17 @@
class PostStatusPolicy < ApplicationPolicy
def create?
user.admin?
end
def update?
user.admin?
end
def destroy?
user.admin?
end
def update_order?
user.admin?
end
end

View File

@@ -0,0 +1,34 @@
class SendNotificationForCommentWorkflow
attr_accessor :comment
def initialize(comment: "")
@comment = comment
end
def run
if comment.is_post_update # Post update
UserMailer.notify_followers_of_post_update(comment: comment).deliver_later
return
end
if comment.parent_id == nil # Reply to a post
user = comment.post.user
if comment.user.id != user.id and
user.notifications_enabled? and
comment.post.follows.exists?(user_id: user.id)
UserMailer.notify_post_owner(comment: comment).deliver_later
end
else # Reply to a comment
parent_comment = comment.parent
user = parent_comment.user
if user.notifications_enabled? and
parent_comment.user.id != comment.user.id
UserMailer.notify_comment_owner(comment: comment).deliver_later
end
end
end
end

View File

@@ -251,7 +251,7 @@ Devise.setup do |config|
# should add them to the navigational formats lists. # should add them to the navigational formats lists.
# #
# The "*/*" below is required to match Internet Explorer requests. # The "*/*" below is required to match Internet Explorer requests.
# config.navigational_formats = ['*/*', :html] config.navigational_formats = ['*/*', :html]
# The default HTTP method used to sign out a resource. Default is :delete. # The default HTTP method used to sign out a resource. Default is :delete.
config.sign_out_via = :delete config.sign_out_via = :delete

View File

@@ -0,0 +1,61 @@
require 'rails_helper'
RSpec.describe 'requests to posts controller', type: :request do
let(:user) { FactoryBot.create(:user) }
let(:moderator) { FactoryBot.create(:moderator) }
let(:admin) { FactoryBot.create(:admin) }
let(:p) { FactoryBot.create(:post) }
let(:board) { FactoryBot.build_stubbed(:board) }
let(:headers) { headers = { "ACCEPT" => "application/json" } }
context 'when user is not logged in' do
it 'fulfills index action' do
get posts_path
expect(response).to have_http_status(:success)
end
it 'fulfills show action' do
get posts_path(p)
expect(response).to have_http_status(:success)
end
it 'blocks create action' do
post posts_path, params: { post: { title: p.title } }, headers: headers
expect(response).to have_http_status(:unauthorized)
end
it 'blocks update action' do
patch post_path(p), params: { post: { title: p.title } }, headers: headers
expect(response).to have_http_status(:unauthorized)
end
end
context 'user role' do
before(:each) do
user.confirm
sign_in user
end
# it 'fulfills create action' do
# post posts_path, params: { post: { title: p.title, board_id: board.id, user_id: user.id } }, headers: headers
# expect(response).to have_http_status(:success)
# end
it 'blocks update action if from different user' do
expect(user.id).not_to eq(p.user_id)
patch post_path(p), params: { post: { title: p.title } }, headers: headers
expect(response).to have_http_status(:unauthorized)
end
end
context 'moderator role' do
before(:each) do
moderator.confirm
sign_in moderator
end
it 'fulfills update action' do
expect(user.id).not_to eq(p.user_id)
patch post_path(p), params: { post: { title: p.title } }, headers: headers
expect(response).to have_http_status(:success)
end
end
end