FIX: Generate webhook payloads before destroy events (#6325)
This commit is contained in:
parent
5b630f3188
commit
8430ea927e
|
@ -8,6 +8,7 @@ class Category < ActiveRecord::Base
|
||||||
include HasCustomFields
|
include HasCustomFields
|
||||||
include CategoryHashtag
|
include CategoryHashtag
|
||||||
include AnonCacheInvalidator
|
include AnonCacheInvalidator
|
||||||
|
include HasDestroyedWebHook
|
||||||
|
|
||||||
REQUIRE_TOPIC_APPROVAL = 'require_topic_approval'
|
REQUIRE_TOPIC_APPROVAL = 'require_topic_approval'
|
||||||
REQUIRE_REPLY_APPROVAL = 'require_reply_approval'
|
REQUIRE_REPLY_APPROVAL = 'require_reply_approval'
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
module HasDestroyedWebHook
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
around_destroy :enqueue_destroyed_web_hook
|
||||||
|
end
|
||||||
|
|
||||||
|
def enqueue_destroyed_web_hook
|
||||||
|
type = self.class.name.underscore.to_sym
|
||||||
|
payload = WebHook.generate_payload(type, self)
|
||||||
|
yield
|
||||||
|
WebHook.enqueue_hooks(type, "#{type}_destroyed".to_sym,
|
||||||
|
id: id,
|
||||||
|
payload: payload
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,6 +5,7 @@ require_dependency 'enum'
|
||||||
class Group < ActiveRecord::Base
|
class Group < ActiveRecord::Base
|
||||||
include HasCustomFields
|
include HasCustomFields
|
||||||
include AnonCacheInvalidator
|
include AnonCacheInvalidator
|
||||||
|
include HasDestroyedWebHook
|
||||||
|
|
||||||
cattr_accessor :preloaded_custom_field_names
|
cattr_accessor :preloaded_custom_field_names
|
||||||
self.preloaded_custom_field_names = Set.new
|
self.preloaded_custom_field_names = Set.new
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
class Tag < ActiveRecord::Base
|
class Tag < ActiveRecord::Base
|
||||||
include Searchable
|
include Searchable
|
||||||
|
include HasDestroyedWebHook
|
||||||
|
|
||||||
validates :name, presence: true, uniqueness: true
|
validates :name, presence: true, uniqueness: true
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ class User < ActiveRecord::Base
|
||||||
include Roleable
|
include Roleable
|
||||||
include HasCustomFields
|
include HasCustomFields
|
||||||
include SecondFactorManager
|
include SecondFactorManager
|
||||||
|
include HasDestroyedWebHook
|
||||||
|
|
||||||
has_many :posts
|
has_many :posts
|
||||||
has_many :notifications, dependent: :destroy
|
has_many :notifications, dependent: :destroy
|
||||||
|
|
|
@ -41,24 +41,21 @@ class WebHook < ActiveRecord::Base
|
||||||
.distinct
|
.distinct
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.enqueue_hooks(type, opts = {})
|
def self.enqueue_hooks(type, event, opts = {})
|
||||||
active_web_hooks(type).each do |web_hook|
|
active_web_hooks(type).each do |web_hook|
|
||||||
Jobs.enqueue(:emit_web_hook_event, opts.merge(
|
Jobs.enqueue(:emit_web_hook_event, opts.merge(
|
||||||
web_hook_id: web_hook.id, event_type: type.to_s
|
web_hook_id: web_hook.id, event_name: event.to_s, event_type: type.to_s
|
||||||
))
|
))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.enqueue_object_hooks(type, object, event, serializer = nil)
|
def self.enqueue_object_hooks(type, object, event, serializer = nil)
|
||||||
if active_web_hooks(type).exists?
|
if active_web_hooks(type).exists?
|
||||||
serializer ||= "WebHook#{type.capitalize}Serializer".constantize
|
payload = WebHook.generate_payload(type, object, serializer)
|
||||||
|
|
||||||
WebHook.enqueue_hooks(type,
|
WebHook.enqueue_hooks(type, event,
|
||||||
event_name: event.to_s,
|
id: object.id,
|
||||||
payload: serializer.new(object,
|
payload: payload
|
||||||
scope: self.guardian,
|
|
||||||
root: false
|
|
||||||
).to_json
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -66,31 +63,38 @@ class WebHook < ActiveRecord::Base
|
||||||
def self.enqueue_topic_hooks(event, topic)
|
def self.enqueue_topic_hooks(event, topic)
|
||||||
if active_web_hooks('topic').exists?
|
if active_web_hooks('topic').exists?
|
||||||
topic_view = TopicView.new(topic.id, Discourse.system_user)
|
topic_view = TopicView.new(topic.id, Discourse.system_user)
|
||||||
|
payload = WebHook.generate_payload(:topic, topic_view, WebHookTopicViewSerializer)
|
||||||
|
|
||||||
WebHook.enqueue_hooks(:topic,
|
WebHook.enqueue_hooks(:topic, event,
|
||||||
|
id: topic.id,
|
||||||
category_id: topic&.category_id,
|
category_id: topic&.category_id,
|
||||||
event_name: event.to_s,
|
payload: payload
|
||||||
payload: WebHookTopicViewSerializer.new(topic_view,
|
|
||||||
scope: self.guardian,
|
|
||||||
root: false
|
|
||||||
).to_json
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.enqueue_post_hooks(event, post)
|
def self.enqueue_post_hooks(event, post)
|
||||||
if active_web_hooks('post').exists?
|
if active_web_hooks('post').exists?
|
||||||
WebHook.enqueue_hooks(:post,
|
payload = WebHook.generate_payload(:post, post)
|
||||||
|
|
||||||
|
WebHook.enqueue_hooks(:post, event,
|
||||||
|
id: post.id,
|
||||||
category_id: post&.topic&.category_id,
|
category_id: post&.topic&.category_id,
|
||||||
event_name: event.to_s,
|
payload: payload
|
||||||
payload: WebHookPostSerializer.new(post,
|
|
||||||
scope: self.guardian,
|
|
||||||
root: false
|
|
||||||
).to_json
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.generate_payload(type, object, serializer = nil)
|
||||||
|
serializer ||= TagSerializer if type == :tag
|
||||||
|
serializer ||= "WebHook#{type.capitalize}Serializer".constantize
|
||||||
|
|
||||||
|
serializer.new(object,
|
||||||
|
scope: self.guardian,
|
||||||
|
root: false
|
||||||
|
).to_json
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def self.guardian
|
def self.guardian
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
%i(
|
%i(
|
||||||
topic_destroyed
|
|
||||||
topic_recovered
|
topic_recovered
|
||||||
).each do |event|
|
).each do |event|
|
||||||
DiscourseEvent.on(event) do |topic, _|
|
DiscourseEvent.on(event) do |topic, _|
|
||||||
|
@ -17,7 +16,6 @@ end
|
||||||
|
|
||||||
%i(
|
%i(
|
||||||
post_created
|
post_created
|
||||||
post_destroyed
|
|
||||||
post_recovered
|
post_recovered
|
||||||
).each do |event|
|
).each do |event|
|
||||||
DiscourseEvent.on(event) do |post, _, _|
|
DiscourseEvent.on(event) do |post, _, _|
|
||||||
|
@ -41,7 +39,6 @@ end
|
||||||
user_logged_in
|
user_logged_in
|
||||||
user_approved
|
user_approved
|
||||||
user_updated
|
user_updated
|
||||||
user_destroyed
|
|
||||||
).each do |event|
|
).each do |event|
|
||||||
DiscourseEvent.on(event) do |user|
|
DiscourseEvent.on(event) do |user|
|
||||||
WebHook.enqueue_object_hooks(:user, user, event)
|
WebHook.enqueue_object_hooks(:user, user, event)
|
||||||
|
@ -51,7 +48,6 @@ end
|
||||||
%i(
|
%i(
|
||||||
group_created
|
group_created
|
||||||
group_updated
|
group_updated
|
||||||
group_destroyed
|
|
||||||
).each do |event|
|
).each do |event|
|
||||||
DiscourseEvent.on(event) do |group|
|
DiscourseEvent.on(event) do |group|
|
||||||
WebHook.enqueue_object_hooks(:group, group, event)
|
WebHook.enqueue_object_hooks(:group, group, event)
|
||||||
|
@ -61,7 +57,6 @@ end
|
||||||
%i(
|
%i(
|
||||||
category_created
|
category_created
|
||||||
category_updated
|
category_updated
|
||||||
category_destroyed
|
|
||||||
).each do |event|
|
).each do |event|
|
||||||
DiscourseEvent.on(event) do |category|
|
DiscourseEvent.on(event) do |category|
|
||||||
WebHook.enqueue_object_hooks(:category, category, event)
|
WebHook.enqueue_object_hooks(:category, category, event)
|
||||||
|
@ -71,7 +66,6 @@ end
|
||||||
%i(
|
%i(
|
||||||
tag_created
|
tag_created
|
||||||
tag_updated
|
tag_updated
|
||||||
tag_destroyed
|
|
||||||
).each do |event|
|
).each do |event|
|
||||||
DiscourseEvent.on(event) do |tag|
|
DiscourseEvent.on(event) do |tag|
|
||||||
WebHook.enqueue_object_hooks(:tag, tag, event, TagSerializer)
|
WebHook.enqueue_object_hooks(:tag, tag, event, TagSerializer)
|
||||||
|
|
|
@ -45,6 +45,14 @@ class PostDestroyer
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
payload = WebHook.generate_payload(:post, @post)
|
||||||
|
topic = @post.topic
|
||||||
|
|
||||||
|
if @post.is_first_post? && topic
|
||||||
|
topic_view = TopicView.new(topic.id, Discourse.system_user)
|
||||||
|
topic_payload = WebHook.generate_payload(:topic, topic_view, WebHookTopicViewSerializer)
|
||||||
|
end
|
||||||
|
|
||||||
delete_removed_posts_after = @opts[:delete_removed_posts_after] || SiteSetting.delete_removed_posts_after
|
delete_removed_posts_after = @opts[:delete_removed_posts_after] || SiteSetting.delete_removed_posts_after
|
||||||
|
|
||||||
if @user.staff? || delete_removed_posts_after < 1
|
if @user.staff? || delete_removed_posts_after < 1
|
||||||
|
@ -53,9 +61,19 @@ class PostDestroyer
|
||||||
mark_for_deletion(delete_removed_posts_after)
|
mark_for_deletion(delete_removed_posts_after)
|
||||||
end
|
end
|
||||||
DiscourseEvent.trigger(:post_destroyed, @post, @opts, @user)
|
DiscourseEvent.trigger(:post_destroyed, @post, @opts, @user)
|
||||||
|
WebHook.enqueue_hooks(:post, :post_destroyed,
|
||||||
|
id: @post.id,
|
||||||
|
category_id: @post&.topic&.category_id,
|
||||||
|
payload: payload
|
||||||
|
)
|
||||||
|
|
||||||
if @post.is_first_post? && @post.topic
|
if @post.is_first_post? && @post.topic
|
||||||
DiscourseEvent.trigger(:topic_destroyed, @post.topic, @user)
|
DiscourseEvent.trigger(:topic_destroyed, @post.topic, @user)
|
||||||
|
WebHook.enqueue_hooks(:topic, :topic_destroyed,
|
||||||
|
id: topic.id,
|
||||||
|
category_id: topic&.category_id,
|
||||||
|
payload: topic_payload
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -424,6 +424,16 @@ describe PostDestroyer do
|
||||||
}.to_not change { author.topic_count }
|
}.to_not change { author.topic_count }
|
||||||
expect(author.post_count).to eq(0) # also unchanged
|
expect(author.post_count).to eq(0) # also unchanged
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'triggers the extensibility events' do
|
||||||
|
events = DiscourseEvent.track_events { PostDestroyer.new(admin, first_post).destroy }.last(2)
|
||||||
|
|
||||||
|
expect(events[0][:event_name]).to eq(:post_destroyed)
|
||||||
|
expect(events[0][:params].first).to eq(first_post)
|
||||||
|
|
||||||
|
expect(events[1][:event_name]).to eq(:topic_destroyed)
|
||||||
|
expect(events[1][:params].first).to eq(first_post.topic)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'deleting the second post in a topic' do
|
context 'deleting the second post in a topic' do
|
||||||
|
@ -502,6 +512,13 @@ describe PostDestroyer do
|
||||||
it "creates a new user history entry" do
|
it "creates a new user history entry" do
|
||||||
expect { subject }.to change { UserHistory.count }.by(1)
|
expect { subject }.to change { UserHistory.count }.by(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'triggers a extensibility event' do
|
||||||
|
events = DiscourseEvent.track_events { subject }
|
||||||
|
|
||||||
|
expect(events[0][:event_name]).to eq(:post_destroyed)
|
||||||
|
expect(events[0][:params].first).to eq(post)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,10 @@ RSpec.describe Jobs::FixPrimaryEmailsForStagedUsers do
|
||||||
|
|
||||||
UserEmail.delete_all
|
UserEmail.delete_all
|
||||||
|
|
||||||
|
# since we removing `user_emails` table the `user.primary_email` value will be nil.
|
||||||
|
# it will raise error in https://github.com/discourse/discourse/blob/d0b027d88deeabf8bc105419f7d3fae0087091cd/app/models/user.rb#L942
|
||||||
|
WebHook.stubs(:generate_payload).returns(nil)
|
||||||
|
|
||||||
expect { described_class.new.execute_onceoff({}) }
|
expect { described_class.new.execute_onceoff({}) }
|
||||||
.to change { User.count }.by(-2)
|
.to change { User.count }.by(-2)
|
||||||
.and change { staged_user.posts.count }.by(3)
|
.and change { staged_user.posts.count }.by(3)
|
||||||
|
|
|
@ -82,7 +82,7 @@ describe WebHook do
|
||||||
describe '#enqueue_hooks' do
|
describe '#enqueue_hooks' do
|
||||||
it 'accepts additional parameters' do
|
it 'accepts additional parameters' do
|
||||||
payload = { test: 'some payload' }.to_json
|
payload = { test: 'some payload' }.to_json
|
||||||
WebHook.enqueue_hooks(:post, payload: payload)
|
WebHook.enqueue_hooks(:post, :post_created, payload: payload)
|
||||||
|
|
||||||
job_args = Jobs::EmitWebHookEvent.jobs.first["args"].first
|
job_args = Jobs::EmitWebHookEvent.jobs.first["args"].first
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ describe WebHook do
|
||||||
|
|
||||||
describe '#enqueue_hooks' do
|
describe '#enqueue_hooks' do
|
||||||
it 'enqueues hooks with ids' do
|
it 'enqueues hooks with ids' do
|
||||||
WebHook.enqueue_hooks(:post)
|
WebHook.enqueue_hooks(:post, :post_created)
|
||||||
|
|
||||||
job_args = Jobs::EmitWebHookEvent.jobs.first["args"].first
|
job_args = Jobs::EmitWebHookEvent.jobs.first["args"].first
|
||||||
|
|
||||||
|
@ -292,12 +292,15 @@ describe WebHook do
|
||||||
payload = JSON.parse(job_args["payload"])
|
payload = JSON.parse(job_args["payload"])
|
||||||
expect(payload["id"]).to eq(user.id)
|
expect(payload["id"]).to eq(user.id)
|
||||||
|
|
||||||
|
email = user.email
|
||||||
|
user.reload
|
||||||
UserDestroyer.new(Discourse.system_user).destroy(user)
|
UserDestroyer.new(Discourse.system_user).destroy(user)
|
||||||
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
|
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
|
||||||
|
|
||||||
expect(job_args["event_name"]).to eq("user_destroyed")
|
expect(job_args["event_name"]).to eq("user_destroyed")
|
||||||
payload = JSON.parse(job_args["payload"])
|
payload = JSON.parse(job_args["payload"])
|
||||||
expect(payload["id"]).to eq(user.id)
|
expect(payload["id"]).to eq(user.id)
|
||||||
|
expect(payload["email"]).to eq(email)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should enqueue the right hooks for category events' do
|
it 'should enqueue the right hooks for category events' do
|
||||||
|
|
Loading…
Reference in New Issue