Skip to content

Commit

Permalink
GraphQL - Post Lock (#875)
Browse files Browse the repository at this point in the history
  • Loading branch information
toyhammered authored Nov 11, 2020
1 parent cf03c58 commit db0da1e
Show file tree
Hide file tree
Showing 19 changed files with 208 additions and 3 deletions.
31 changes: 31 additions & 0 deletions app/graphql/mutations/post/lock_post.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class Mutations::Post::LockPost < Mutations::Base
argument :input,
Types::Input::Post::Lock,
required: true,
description: 'Lock a Post.',
as: :post

field :post, Types::Post, null: true

def load_post(value)
post = ::Post.find(value.id)
post.assign_attributes(value.to_model)
post
end

def authorized?(post:)
super(post, :lock?)
end

def resolve(post:)
post.save

if post.errors.any?
Errors::RailsModel.graphql_error(post)
else
{
post: post
}
end
end
end
31 changes: 31 additions & 0 deletions app/graphql/mutations/post/unlock_post.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class Mutations::Post::UnlockPost < Mutations::Base
argument :input,
Types::Input::Post::Unlock,
required: true,
description: 'Unlock a Post.',
as: :post

field :post, Types::Post, null: true

def load_post(value)
post = ::Post.find(value.id)
post.assign_attributes(value.to_model)
post
end

def authorized?(post:)
super(post, :unlock?)
end

def resolve(post:)
post.save

if post.errors.any?
Errors::RailsModel.graphql_error(post)
else
{
post: post
}
end
end
end
5 changes: 5 additions & 0 deletions app/graphql/types/enum/locked_reason.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Types::Enum::LockedReason < Types::Enum::Base
value 'SPAM', value: 'spam'
value 'TOO_HEATED', value: 'too_heated'
value 'CLOSED', value: 'closed'
end
4 changes: 4 additions & 0 deletions app/graphql/types/input/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ def self.default_graphql_name
def to_model
to_h
end

def current_user
User.current
end
end
8 changes: 8 additions & 0 deletions app/graphql/types/input/post/lock.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class Types::Input::Post::Lock < Types::Input::Base
argument :id, ID, required: true
argument :locked_reason, Types::Enum::LockedReason, required: true

def to_model
to_h.merge(locked_at: DateTime.current, locked_by: current_user)
end
end
7 changes: 7 additions & 0 deletions app/graphql/types/input/post/unlock.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Types::Input::Post::Unlock < Types::Input::Base
argument :id, ID, required: true

def to_model
to_h.merge(locked_at: nil, locked_by: nil, locked_reason: nil)
end
end
1 change: 1 addition & 0 deletions app/graphql/types/mutation_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ class Types::MutationType < Types::BaseObject
field :episode, Types::Mutations::EpisodeMutation, null: true
field :library_entry, Types::Mutations::LibraryEntryMutation, null: true
field :mapping, Types::Mutations::MappingMutation, null: true
field :post, Types::Mutations::PostMutation, null: true
end
9 changes: 9 additions & 0 deletions app/graphql/types/mutations/post_mutation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class Types::Mutations::PostMutation < Types::BaseObject
field :lock,
mutation: ::Mutations::Post::LockPost,
description: 'Lock a Post.'

field :unlock,
mutation: ::Mutations::Post::UnlockPost,
description: 'Unlock a Post.'
end
12 changes: 12 additions & 0 deletions app/graphql/types/post.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ class Types::Post < Types::BaseObject
null: false,
description: 'Html formatted content.'

field :locked_by, Types::Profile,
null: true,
description: 'The user who locked this post.'

field :locked_at, GraphQL::Types::ISO8601DateTime,
null: true,
description: 'When this post was locked.'

field :locked_reason, Types::Enum::LockedReason,
null: true,
description: 'The reason why this post was locked.'

field :comments, Types::Comment.connection_type,
null: false,
description: 'All comments related to this post.'
Expand Down
6 changes: 6 additions & 0 deletions app/models/post.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@ class Post < ApplicationRecord
update_algolia 'AlgoliaPostsIndex'
embed_links_in :content, to: :embed

enum locked_reason: { spam: 0, too_heated: 1, closed: 2 }
belongs_to :user, required: true
belongs_to :edited_by, class_name: 'User'
belongs_to :target_user, class_name: 'User'
belongs_to :target_group, class_name: 'Group'
belongs_to :media, polymorphic: true
belongs_to :spoiled_unit, polymorphic: true
belongs_to :community_recommendation
belongs_to :locked_by, class_name: 'User'
has_many :post_likes, dependent: :destroy
has_many :post_follows, dependent: :destroy
has_many :comments, dependent: :destroy
Expand Down Expand Up @@ -136,6 +138,10 @@ def mentioned_users
User.where(id: processed_content[:mentioned_users])
end

def locked?
locked_by.present?
end

before_save do
# Always check if the media is NSFW and try to force into NSFWness
self.nsfw = media.try(:nsfw?) || false unless nsfw
Expand Down
10 changes: 9 additions & 1 deletion app/policies/comment_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ def update?
return false unless user
return false if user.has_role?(:banned)
return true if can_administrate?
# admins are allowed to update comments on locked posts
return false if record.post.locked?
return true if group && has_group_permission?(:content)
is_owner?
end
Expand All @@ -19,7 +21,13 @@ def create?
return false if banned_from_group?
return false if group.closed? && !member?
end
is_owner?

# admins are allowed to create comments on locked posts
if record.post.locked?
is_owner? && can_administrate?
else
is_owner?
end
end

def destroy?
Expand Down
15 changes: 15 additions & 0 deletions app/policies/post_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ def update?
return false unless user
return false if user.has_role?(:banned)
return true if can_administrate?
return false if record.locked?
return true if group && has_group_permission?(:content)
is_owner?
end
Expand Down Expand Up @@ -36,6 +37,20 @@ def group
record.target_group
end

def lock?
return true if can_administrate? || is_owner?
return true if group && has_group_permission?(:content)

false
end

def unlock?
return true if can_administrate?
return true if group && has_group_permission?(:content)

false
end

class Scope < Scope
def resolve
return scope if can_administrate?
Expand Down
13 changes: 12 additions & 1 deletion app/resources/post_resource.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
class PostResource < BaseResource
caching
IMMUTABLE_FIELDS = %i[locked_by locked_at locked_reason].freeze

attributes :content, :content_formatted, :comments_count, :post_likes_count,
:spoiler, :nsfw, :blocked, :deleted_at, :top_level_comments_count,
:edited_at, :target_interest, :embed, :embed_url

attributes(*IMMUTABLE_FIELDS)

has_one :user
has_one :target_user
has_one :target_group
Expand All @@ -15,11 +18,19 @@ class PostResource < BaseResource
has_many :comments
has_many :uploads

def self.creatable_fields(context)
super - IMMUTABLE_FIELDS
end

def self.updatable_fields(context)
super - IMMUTABLE_FIELDS
end

def target_interest=(val)
_model.target_interest = val.underscore.classify
end

def target_interest
_model.target_interest.underscore.dasherize if _model.target_interest
_model&.target_interest&.underscore&.dasherize
end
end
8 changes: 8 additions & 0 deletions db/migrate/20201030020815_add_lock_unlock_to_post.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class AddLockUnlockToPost < ActiveRecord::Migration[5.1]
def change
add_column :posts, :locked_by_id, :integer
add_column :posts, :locked_at, :datetime
add_column :posts, :locked_reason, :integer
add_index :posts, :locked_by_id
end
end
6 changes: 5 additions & 1 deletion 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: 20200822214657) do
ActiveRecord::Schema.define(version: 20201030020815) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -1216,9 +1216,13 @@
t.integer "community_recommendation_id"
t.string "ao_id"
t.integer "edited_by_id"
t.integer "locked_by_id"
t.datetime "locked_at"
t.integer "locked_reason"
t.index ["ao_id"], name: "index_posts_on_ao_id", unique: true
t.index ["community_recommendation_id"], name: "index_posts_on_community_recommendation_id"
t.index ["deleted_at"], name: "index_posts_on_deleted_at"
t.index ["locked_by_id"], name: "index_posts_on_locked_by_id"
t.index ["media_type", "media_id"], name: "posts_media_type_media_id_idx"
t.index ["target_group_id"], name: "posts_target_group_id_idx"
t.index ["target_user_id"], name: "posts_target_user_id_idx"
Expand Down
6 changes: 6 additions & 0 deletions spec/factories/posts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,11 @@
factory :post do
user
content { Faker::Lorem.sentence }

trait :locked do
association :locked_by, factory: :user, strategy: :build
locked_at { DateTime.now }
locked_reason { :spam }
end
end
end
1 change: 1 addition & 0 deletions spec/models/post_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
it { should belong_to(:target_user).class_name('User') }
it { should belong_to(:media) }
it { should belong_to(:spoiled_unit) }
it { should belong_to(:locked_by).class_name('User') }
it { should have_many(:post_likes).dependent(:destroy) }
it { should have_many(:comments).dependent(:destroy) }
it { should validate_length_of(:content).is_at_most(9_000) }
Expand Down
17 changes: 17 additions & 0 deletions spec/policies/comment_policy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,30 @@
it('should allow community mod') { should permit(community_mod, comment) }
it('should not allow other users') { should_not permit(other, comment) }
it('should not allow anons') { should_not permit(nil, comment) }

context 'when post is locked' do
let(:post) { build(:post, :locked, user: owner.resource_owner) }
let(:comment) { build(:comment, user: community_mod.resource_owner, post: post) }

it('should not allow a regular user') { should_not permit(other, comment) }
it('should allow a community_mod') { should permit(community_mod, comment) }
end
end

permissions :create? do
it('should allow owner') { should permit(owner, comment) }
it('should not allow community mod') { should_not permit(community_mod, comment) }
it('should not allow random dude') { should_not permit(other, comment) }
it('should not allow anon') { should_not permit(nil, comment) }

context 'when post is locked' do
let(:post) { build(:post, :locked, user: owner.resource_owner) }
let(:community_mod_comment) { build(:comment, user: community_mod.resource_owner, post: post) }
let(:owner_comment) { build(:comment, user: owner.resource_owner, post: post) }

it('should not allow regular owner') { should_not permit(owner, owner_comment) }
it('should only allow community_mod') { should permit(community_mod, community_mod_comment) }
end
end

permissions :destroy? do
Expand Down
21 changes: 21 additions & 0 deletions spec/policies/post_policy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@
it('should allow community mod') { should permit(community_mod, post) }
it('should not allow other users') { should_not permit(other, post) }
it('should not allow anons') { should_not permit(nil, post) }

context 'when post is locked' do
let(:post) { build(:post, :locked, user: owner.resource_owner) }

it('should not allow regular user') { should_not permit(owner, post) }
it('should allow community_mod') { should permit(community_mod, post) }
end
end

permissions :lock? do
let(:post) { build(:post, :locked, user: owner.resource_owner) }

it('should allow community_mod') { should permit(community_mod, post) }
it('should allow owner') { should permit(owner, post) }
end

permissions :unlock? do
let(:post) { build(:post, :locked, user: owner.resource_owner) }

it('should allow community_mod') { should permit(community_mod, post) }
it('should not allow owner') { should_not permit(owner, post) }
end

permissions :create? do
Expand Down

0 comments on commit db0da1e

Please sign in to comment.