Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,5 @@ group :development, :test do
end

gem "positioning", "~> 0.4.7"

gem "action_policy", "~> 0.7.5"
6 changes: 5 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
GEM
remote: https://rubygems.org/
specs:
action_text-trix (2.1.16)
action_policy (0.7.5)
ruby-next-core (>= 1.0)
action_text-trix (2.1.15)
railties
actioncable (8.1.0)
actionpack (= 8.1.0)
Expand Down Expand Up @@ -409,6 +411,7 @@ GEM
rubocop (>= 1.72)
rubocop-performance (>= 1.24)
rubocop-rails (>= 2.30)
ruby-next-core (1.1.2)
ruby-progressbar (1.13.0)
ruby-vips (2.2.5)
ffi (~> 1.12)
Expand Down Expand Up @@ -495,6 +498,7 @@ PLATFORMS
ruby

DEPENDENCIES
action_policy (~> 0.7.5)
active_storage_validations (~> 3.0)
ahoy_matey
apipie-rails (~> 1.5.0)
Expand Down
14 changes: 14 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
class ApplicationController < ActionController::Base
include ApplicationHelper
prepend ActionPolicy::Draper
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :authenticate_user! # ensures only logged-in users can access pages

# TODO add this after_action callback to verify
# that `authorize!` has been called in all controllers
# once all policies are added
#
# verify_authorized
#

rescue_from ActionPolicy::Unauthorized do |exception|
flash[:alert] = exception.message.presence || "You are not authorized to perform this action."
redirect_back_or_to root_link_path # TODO handle unauthenticated
end

private

def after_sign_in_path_for(resource)
Expand Down
36 changes: 25 additions & 11 deletions app/controllers/resources_controller.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
class ResourcesController < ApplicationController
include ExternallyRedirectable

def index
authorize!

if turbo_frame_request?
per_page = params[:number_of_items_per_page].presence || 25
unfiltered = Resource.where(kind: Resource::PUBLISHED_KINDS) # TODO - #FIXME brittle
.includes(:primary_asset, :gallery_assets, :attachments)
filtered = unfiltered.search_by_params(params)
.by_created
@resources = filtered.paginate(page: params[:page], per_page: per_page)

total_count = unfiltered.count
base_scope =
authorized_scope(Resource.where(kind: Resource::PUBLISHED_KINDS)) # TODO - #FIXME brittle
.includes(:primary_asset, :gallery_assets, :attachments)

filtered =
base_scope
.search_by_params(params)
.by_created

@resources =
filtered.paginate(page: params[:page], per_page: per_page)

total_count = base_scope.count
filtered_count = filtered.count
@count_display = if filtered_count == total_count
total_count
else
"#{filtered_count}/#{total_count}"
end
@count_display =
filtered_count == total_count ? total_count : "#{filtered_count}/#{total_count}"

render :resource_results
else
Expand All @@ -24,6 +31,7 @@ def index
end

def stories
authorize!
@stories = Resource.story.paginate(page: params[:page], per_page: 6).decorate
end

Expand All @@ -34,6 +42,7 @@ def new

def edit
@resource = Resource.find(resource_id_param).decorate
authorize! @resource
set_form_variables

if turbo_frame_request?
Expand All @@ -45,11 +54,13 @@ def edit

def show
@resource = Resource.find(resource_id_param).decorate
authorize! @resource
@resource.increment_view_count!(session: session, request: request)
load_forms
end

def create
authorize!
@resource = current_user.resources.build(resource_params)
if @resource.save
redirect_to resources_path
Expand All @@ -63,6 +74,7 @@ def create

def update
@resource = Resource.find(params[:id])
authorize! @resource
@resource.user ||= current_user
if @resource.update(resource_params)
flash[:notice] = "Resource updated."
Expand All @@ -76,6 +88,7 @@ def update

def destroy
@resource = Resource.find(params[:id])
authorize! @resource
@resource.destroy!
redirect_to resources_path, notice: "Resource was successfully destroyed."
end
Expand All @@ -88,6 +101,7 @@ def search

def download
@resource = Resource.find(params[:resource_id])
authorize! @resource
@resource.increment!(:download_count)

attachment = if params[:attachment_id].to_i > 0
Expand Down
7 changes: 7 additions & 0 deletions app/models/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ def month
def attachable_content_type
"application/vnd.active_record.resource"
end
def published?
!inactive? && published_kind?
end

def published_kind?
self.class::PUBLISHED_KINDS.include?(kind)
end

private
def self.reject?(resource)
Expand Down
30 changes: 30 additions & 0 deletions app/policies/application_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Base class for application policies
class ApplicationPolicy < ActionPolicy::Base
# Configure additional authorization contexts here
# (`user` is added by default).
#
# authorize :account, optional: true
#
# Read more about authorization context: https://actionpolicy.evilmartians.io/#/authorization_context
#
pre_check :verify_authenticated!

private
# Define shared methods useful for most policies.

def admin?
user.super_user?
end

def owner?
record.user_id == user.id
end

def authenticated? = user.present?

def unauthenticated? = !authenticated?

def verify_authenticated!
deny! if unauthenticated?
end
end
38 changes: 38 additions & 0 deletions app/policies/resource_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class ResourcePolicy < ApplicationPolicy
# See https://actionpolicy.evilmartians.io/#/writing_policies
#
alias_rule :edit?, :destroy?, to: :update?
alias_rule :rhino_text?, to: :show?

def index?
true
end

def download?
true
end

def show?
admin? || record.published?
end

def new?
admin?
end

def update?
admin? || owner?
end

def filter_published?
admin?
end

relation_scope do |relation|
if admin?
relation
else
relation.published
end
end
end
36 changes: 17 additions & 19 deletions app/views/resources/_resource_card.html.erb
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
<div class="relative">

<!-- BOOKMARK ICON -->
<div class="absolute top-3 right-3 z-30">
<%= render "bookmarks/editable_bookmark_icon", resource: resource.object %>
</div>

<!-- CARD -->
<div class="
flex flex-row flex-wrap
bg-white border border-gray-200 rounded-xl shadow-sm hover:shadow-md
transition-all overflow-hidden">

<div
class="
flex flex-row flex-wrap
bg-white border border-gray-200 rounded-xl shadow-sm hover:shadow-md
transition-all overflow-hidden
"
>
<!-- LEFT SIDE -->
<div class="
flex-shrink-0
w-40 md:w-40
flex flex-col items-center text-center justify-center py-4 pb-3 px-3
border-b md:border-b-0 md:border-r border-gray-200">
<div
class="
flex-shrink-0
w-40 md:w-40
flex flex-col items-center text-center justify-center py-4 pb-3 px-3
border-b md:border-b-0 md:border-r border-gray-200
"
>
<!-- IMAGE -->
<div class="relative w-32 h-32">
<%= link_to resource_path(resource),
data: { turbo_frame: "_top", turbo_prefetch: false },
class: "block w-full h-full" do %>

<!-- fallback icon -->
<div class="absolute inset-0 flex items-center justify-center text-gray-700 resource-icon">
<%= render "resources/resource_icon", kind: resource.kind %>
Expand All @@ -38,9 +41,7 @@
</div>

<!-- TYPE -->
<span class="mt-2 text-xs font-medium text-gray-500 uppercase tracking-wide">
<%= resource.kind_display %>
</span>
<span class="mt-2 text-xs font-medium text-gray-500 uppercase tracking-wide"><%= resource.kind_display %></span>
</div>

<!-- RIGHT SIDE -->
Expand All @@ -56,7 +57,6 @@

<!-- ACTION BUTTONS -->
<div class="absolute bottom-3 right-3 flex items-center space-x-2 z-30">

<% if resource.download_attachment&.file&.attached? %>
<%= link_to(
content_tag(:span, "", class: "fa fa-download"),
Expand All @@ -66,17 +66,15 @@
) %>
<% end %>

<% if controller_name == "resources" && current_user.super_user? %>
<% if controller_name == "resources" && allowed_to?(:edit?, resource) %>
<%= link_to(
content_tag(:span, "", class: "far fa-edit"),
edit_resource_path(resource.object),
class: "admin-only bg-blue-100 btn btn-secondary-outline",
data: {turbo_frame: "_top", turbo_prefetch: false }
) %>
<% end %>

</div>

</div>
</div>
</div>
6 changes: 4 additions & 2 deletions app/views/resources/_search_boxes.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,20 @@
<label for="query" class="block text-xs font-semibold uppercase text-gray-500 tracking-wide mb-1">
Keywords
</label>

<div class="relative">
<%= text_field_tag :query, params[:query],
class: "w-full bg-white border border-gray-300 rounded-lg px-3 py-2 pr-10
focus:ring-blue-500 focus:border-blue-500" %>

<button type="submit" class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700">
<i class="fa fa-search"></i>
</button>
</div>
</div>

<% if current_user.super_user? %>
<!-- PUBLISHED -->
<!-- PUBLISHED -->
<% if allowed_to?(:filter_published?, Resource) %>
<div class="min-w-[150px] admin-only bg-blue-100 p-2 rounded-md">
<label for="published" class="block text-xs font-semibold uppercase text-gray-600 tracking-wide mb-1">
Published
Expand Down
3 changes: 2 additions & 1 deletion app/views/resources/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
<div class="pr-6">
<h2 class="text-2xl font-semibold mb-2">Resources</h2>
</div>

<div class="text-right text-end">
<% if current_user.super_user? %>
<% if allowed_to?(:new?, Resource) %>
<%= link_to "New Resource",
new_resource_path,
class: "admin-only bg-blue-100 btn btn-primary-outline" %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/resources/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<%= render "bookmarks/editable_bookmark_button", resource: @resource.object %>
</span>

<% if current_user.super_user? %>
<% if allowed_to?(:edit?, @resource) %>
<%= link_to("Edit", edit_resource_path(@resource),
class: "admin-only bg-blue-100 btn btn-primary-outline") %>
<% end %>
Expand Down
8 changes: 8 additions & 0 deletions lib/action_policy/draper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module ActionPolicy
module Draper
def policy_for(record:, **opts)
record = record.model while record.is_a?(::Draper::Decorator)
super
end
end
end
21 changes: 21 additions & 0 deletions spec/policies/resource_policy_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require "rails_helper"

RSpec.describe ResourcePolicy, type: :policy do
# See https://actionpolicy.evilmartians.io/#/testing?id=rspec-dsl
#
# let(:user) { build_stubbed :user }
# let(:record) { build_stubbed :post, draft: false }
# let(:context) { {user: user} }

describe_rule :index? do
pending "add some examples to (or delete) #{__FILE__}"
end

describe_rule :create? do
pending "add some examples to (or delete) #{__FILE__}"
end

describe_rule :manage? do
pending "add some examples to (or delete) #{__FILE__}"
end
end
Loading