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'
gem 'rails', '6.0.4.7'
gem 'i18n-js'
gem 'pg', '>= 0.18', '< 2.0'
@@ -23,6 +22,12 @@ gem 'bootsnap', '>= 1.4.2', require: false
# Authentication
gem 'devise', '4.7.3'
# Authorization
gem 'pundit', '2.2.0'
# I18n (forward locales to JS)
gem 'i18n-js'
# Administration panel
gem "administrate", '0.16.0'

View File

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

View File

@@ -1,4 +1,8 @@
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 :load_boards
@@ -12,4 +16,12 @@ class ApplicationController < ActionController::Base
def load_boards
@boards = Board.select(:id, :name).order(order: :asc)
end
private
def user_not_authorized
render json: {
error: t('backend.errors.unauthorized')
}, status: :unauthorized
end
end

View File

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

View File

@@ -23,10 +23,10 @@ class CommentsController < ApplicationController
comment = Comment.new(comment_params)
if comment.save
send_notifications(comment)
SendNotificationForCommentWorkflow.new(comment: comment).run
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
else
render json: {
@@ -37,17 +37,12 @@ class CommentsController < ApplicationController
def update
comment = Comment.find(params[:id])
authorize comment
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
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
render json: {
@@ -57,41 +52,14 @@ class CommentsController < ApplicationController
end
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)
if comment.is_post_update # Post update
UserMailer.notify_followers_of_post_update(comment: comment).deliver_later
return
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
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

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

View File

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