Skip to content

Commit

Permalink
Integrate Pundit and build admin functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
zachlatta committed Jan 27, 2018
1 parent a779993 commit 0415819
Show file tree
Hide file tree
Showing 16 changed files with 211 additions and 36 deletions.
1 change: 1 addition & 0 deletions api/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ gem 'mimemagic', '~> 0.3'
gem 'octokit', '~> 4.7'
gem 'paranoia', '~> 2.4'
gem 'pdfkit', '~> 0.8.2'
gem 'pundit', '~> 1.1'
gem 'rack-cors', require: 'rack/cors'
gem 'redcarpet', '~> 3.4.0'
gem 'redis-rails', '~> 5.0.2'
Expand Down
3 changes: 3 additions & 0 deletions api/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ GEM
method_source (~> 0.9.0)
public_suffix (3.0.1)
puma (3.11.0)
pundit (1.1.0)
activesupport (>= 3.0.0)
rack (2.0.3)
rack-cors (1.0.2)
rack-protection (2.0.0)
Expand Down Expand Up @@ -327,6 +329,7 @@ DEPENDENCIES
pdfkit (~> 0.8.2)
pg (~> 0.18.4)
puma (~> 3.0)
pundit (~> 1.1)
rack-cors
rails (~> 5.1)
redcarpet (~> 3.4.0)
Expand Down
1 change: 1 addition & 0 deletions api/app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# frozen_string_literal: true

# See V1::ApiController for base controller of API V1.
class ApplicationController < ActionController::API
end
5 changes: 5 additions & 0 deletions api/app/controllers/v1/api_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

module V1
class ApiController < ApplicationController
include Pundit

rescue_from Pundit::NotAuthorizedError, with: :render_access_denied
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found

def render_success(obj = { success: true }, status = 200)
render json: obj, status: status
end
Expand Down
4 changes: 4 additions & 0 deletions api/app/controllers/v1/concerns/user_auth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ def authenticate_user
end
end

def current_user
@user
end

protected

def render_unauthenticated
Expand Down
12 changes: 4 additions & 8 deletions api/app/controllers/v1/leader_profiles_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,15 @@ class LeaderProfilesController < ApiController
include UserAuth

def show
profile = LeaderProfile.find_by(id: params[:id])

return render_not_found unless profile
return render_access_denied if profile.user != @user
profile = LeaderProfile.find(params[:id])
authorize profile

render_success(profile)
end

def update
profile = LeaderProfile.find_by(id: params[:id])

return render_not_found unless profile
return render_access_denied if profile.user != @user
profile = LeaderProfile.find(params[:id])
authorize profile

if profile.submitted_at.present?
return render_field_error(
Expand Down
46 changes: 19 additions & 27 deletions api/app/controllers/v1/new_club_applications_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ module V1
class NewClubApplicationsController < ApiController
include UserAuth

# All applications
def full_index
return render_access_denied unless @user.admin?
render_success NewClubApplication.all
end

# Applications for a specific user
def index
if params[:user_id] == @user.id.to_s
render_success(@user.new_club_applications)
Expand All @@ -13,28 +20,21 @@ def index
end

def show
application = NewClubApplication.find_by(id: params[:id])

return render_not_found unless application
application = NewClubApplication.find(params[:id])
authorize application

if application.users.include? @user
render_success(application)
else
render_access_denied
end
render_success(application)
end

def create
c = NewClubApplication.create(users: [@user],
point_of_contact: @user)
c = NewClubApplication.create(users: [@user], point_of_contact: @user)

render_success(c, 201)
end

def update
c = NewClubApplication.find(params[:id])

return render_access_denied unless c.users.include? @user
authorize c

if c.update_attributes(club_application_params)
render_success(c)
Expand All @@ -44,10 +44,8 @@ def update
end

def add_user
app = NewClubApplication.find_by(id: params[:new_club_application_id])

return render_not_found unless app
return render_access_denied unless app.users.include? @user
app = NewClubApplication.find(params[:new_club_application_id])
authorize app

if app.submitted_at.present?
return render_field_error(:base, 'cannot edit application after submit')
Expand All @@ -73,14 +71,10 @@ def add_user
end

def remove_user
app = NewClubApplication.find_by(id: params[:new_club_application_id])
to_remove = User.find_by(id: params[:user_id])

return render_not_found unless app && to_remove
app = NewClubApplication.find(params[:new_club_application_id])
to_remove = User.find(params[:user_id])

return render_access_denied unless app.users.include? @user

return render_access_denied unless app.point_of_contact == @user
authorize app

if app.submitted_at.present?
return render_field_error(:base, 'cannot edit application after submit')
Expand All @@ -99,10 +93,8 @@ def remove_user
end

def submit
app = NewClubApplication.find_by(id: params[:new_club_application_id])

return render_not_found unless app
return render_access_denied unless app.users.include? @user
app = NewClubApplication.find(params[:new_club_application_id])
authorize app

if app.submit!
render_success(app)
Expand Down
12 changes: 12 additions & 0 deletions api/app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,16 @@ def generate_auth_token!
break unless User.find_by(auth_token: auth_token)
end
end

def make_admin!
self.admin_at = Time.current
end

def remove_admin!
self.admin_at = nil
end

def admin?
admin_at.present?
end
end
55 changes: 55 additions & 0 deletions api/app/policies/application_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# 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?
scope.where(id: record.id).exists?
end

def create?
false
end

def new?
create?
end

def update?
false
end

def edit?
update?
end

def destroy?
false
end

def scope
Pundit.policy_scope!(user, record.class)
end

class Scope
attr_reader :user, :scope

def initialize(user, scope)
@user = user
@scope = scope
end

def resolve
scope
end
end
end
17 changes: 17 additions & 0 deletions api/app/policies/leader_profile_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

class LeaderProfilePolicy < ApplicationPolicy
def show?
owns_profile?
end

def update?
owns_profile?
end

private

def owns_profile?
record.user == user
end
end
29 changes: 29 additions & 0 deletions api/app/policies/new_club_application_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

class NewClubApplicationPolicy < ApplicationPolicy
def show?
user_added?
end

def update?
user_added?
end

def add_user?
user_added?
end

def remove_user?
user_added? && record.point_of_contact == user
end

def submit?
user_added?
end

private

def user_added?
record.users.include? user
end
end
2 changes: 2 additions & 0 deletions api/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
resources :donations, only: [:create]

resources :club_applications, only: [:create]

get '/new_club_applications', to: 'new_club_applications#full_index'
resources :new_club_applications, only: %i[show update] do
post 'add_user'
delete 'remove_user'
Expand Down
7 changes: 7 additions & 0 deletions api/db/migrate/20180127102450_add_admin_at_to_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddAdminAtToUser < ActiveRecord::Migration[5.1]
def change
add_column :users, :admin_at, :datetime
end
end
3 changes: 2 additions & 1 deletion api/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20180127084614) do
ActiveRecord::Schema.define(version: 20180127102450) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -341,6 +341,7 @@
t.datetime "auth_token_generation"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "admin_at"
end

add_foreign_key "athul_clubs", "clubs"
Expand Down
23 changes: 23 additions & 0 deletions api/spec/models/user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

it { should have_db_column :email }
it { should have_db_column :login_code }
it { should have_db_column :login_code_generation }
it { should have_db_column :auth_token }
it { should have_db_column :auth_token_generation }
it { should have_db_column :admin_at }

it { should validate_presence_of :email }
it { should validate_email_format_of :email }
Expand Down Expand Up @@ -57,4 +60,24 @@
# changes every time
expect { subject.generate_auth_token! }.to change { subject.auth_token }
end

example ':make_admin!' do
subject.admin_at = nil

subject.make_admin!

expect(subject.admin_at).to be_within(1.second).of(Time.current)
expect(subject.admin?).to eq(true)
end

example ':remove_admin!' do
subject.admin_at = nil

subject.make_admin!
expect(subject.admin?).to eq(true)

subject.remove_admin!
expect(subject.admin_at).to eq(nil)
expect(subject.admin?).to eq(false)
end
end
27 changes: 27 additions & 0 deletions api/spec/requests/v1/new_club_applications_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,33 @@
let(:user) { create(:user_authed) }
let(:auth_headers) { { 'Authorization': "Bearer #{user.auth_token}" } }

describe 'GET /v1/new_club_applications' do
it 'requires authentication' do
get '/v1/new_club_applications'
expect(response.status).to eq(401)
end

it 'requires admin access' do
get '/v1/new_club_applications', headers: auth_headers
expect(response.status).to eq(403)
end

it 'lists all applications' do
my_app = create(:new_club_application)
my_app.users << user

create(:new_club_application) # someone else's application

# make user an admin
user.make_admin! && user.save

get '/v1/new_club_applications', headers: auth_headers
expect(response.status).to eq(200)

expect(json.length).to eq(2)
end
end

describe 'GET /v1/users/:id/new_club_applications' do
it 'requires authentication' do
get "/v1/users/#{user.id}/new_club_applications"
Expand Down

0 comments on commit 0415819

Please sign in to comment.