diff --git a/Gemfile b/Gemfile index 85e843b44..aed818761 100644 --- a/Gemfile +++ b/Gemfile @@ -112,7 +112,6 @@ gem "stripe", "~> 5.42" # Ruby library for the Stripe API gem "strong_migrations", "~> 0.7" # Catch unsafe migrations gem "twitter", "~> 7.0" # A Ruby interface to the Twitter API gem "uglifier", "~> 4.2" # Uglifier minifies JavaScript files -gem "ulid", "~> 1.3" # Universally Unique Lexicographically Sortable Identifier implementation for Ruby gem "validate_url", "~> 1.0" # Library for validating urls in Rails gem "vault", "~> 0.16" # Used to store secrets gem "view_component", "~> 2.47" # View components for Rails diff --git a/Gemfile.lock b/Gemfile.lock index f0dd4292f..b9ac6ba40 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -853,7 +853,6 @@ GEM concurrent-ruby (~> 1.0) uglifier (4.2.0) execjs (>= 0.3.0, < 3) - ulid (1.3.0) unf (0.1.4) unf_ext unf_ext (0.0.8) @@ -1059,7 +1058,6 @@ DEPENDENCIES timecop (~> 0.9) twitter (~> 7.0) uglifier (~> 4.2) - ulid (~> 1.3) validate_url (~> 1.0) vault (~> 0.16) vcr (~> 6.0) diff --git a/app/controllers/admin/webhook_endpoints_controller.rb b/app/controllers/admin/webhook_endpoints_controller.rb deleted file mode 100644 index 2931ae2c3..000000000 --- a/app/controllers/admin/webhook_endpoints_controller.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Admin - class WebhookEndpointsController < Admin::ApplicationController - layout "admin" - - def index - @endpoints = Webhook::Endpoint.includes(:user) - .page(params[:page]).per(50) - .order(created_at: :desc) - end - end -end diff --git a/app/controllers/api/v0/webhooks_controller.rb b/app/controllers/api/v0/webhooks_controller.rb deleted file mode 100644 index e5d94694a..000000000 --- a/app/controllers/api/v0/webhooks_controller.rb +++ /dev/null @@ -1,44 +0,0 @@ -module Api - module V0 - class WebhooksController < ApiController - before_action :authenticate! - - skip_before_action :verify_authenticity_token, only: %w[create destroy] - - ATTRIBUTES_FOR_SERIALIZATION = %i[id user_id source target_url events created_at].freeze - private_constant :ATTRIBUTES_FOR_SERIALIZATION - - def index - @webhooks = webhooks_scope.order(:id) - end - - def create - @webhook = @user.webhook_endpoints.new(webhook_params) - @webhook.save! - - render "show", status: :created - end - - def show - @webhook = webhooks_scope.find(params[:id]) - end - - def destroy - webhook = webhooks_scope.find(params[:id]) - webhook.destroy! - - head :no_content - end - - private - - def webhooks_scope - @user.webhook_endpoints.select(ATTRIBUTES_FOR_SERIALIZATION) - end - - def webhook_params - params.require(:webhook_endpoint).permit(:target_url, :source, events: []) - end - end - end -end diff --git a/app/models/admin_menu.rb b/app/models/admin_menu.rb index 8b9693071..806b72f83 100644 --- a/app/models/admin_menu.rb +++ b/app/models/admin_menu.rb @@ -50,7 +50,6 @@ class AdminMenu item(name: "developer tools", controller: "tools", children: [ item(name: "tools"), item(name: "vault secrets", controller: "secrets"), - item(name: "webhooks", controller: "webhook_endpoints"), item(name: "data update scripts", visible: false), ]), ] diff --git a/app/models/user.rb b/app/models/user.rb index aeec196d9..6f00691ba 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -111,7 +111,6 @@ class User < ApplicationRecord foreign_key: :subscriber_id, inverse_of: :subscriber, dependent: :destroy has_many :subscribers, through: :source_authored_user_subscriptions, dependent: :destroy has_many :tweets, dependent: :nullify - has_many :webhook_endpoints, class_name: "Webhook::Endpoint", inverse_of: :user, dependent: :delete_all has_many :devices, dependent: :delete_all has_many :sponsorships, dependent: :delete_all diff --git a/app/models/webhook/endpoint.rb b/app/models/webhook/endpoint.rb deleted file mode 100644 index afdc64200..000000000 --- a/app/models/webhook/endpoint.rb +++ /dev/null @@ -1,24 +0,0 @@ -module Webhook - class Endpoint < ApplicationRecord - belongs_to :user, inverse_of: :webhook_endpoints - - validates :events, presence: true - validates :source, presence: true - validates :target_url, presence: true, uniqueness: true, url: { schemes: %w[https] } - validates :user_id, presence: true - - attribute :events, :string, array: true, default: [] - - scope :for_events, ->(events) { where("events @> ARRAY[?]::varchar[]", Array(events)) } - scope :for_app, ->(app_id) { where(oauth_application_id: app_id) } - - def self.table_name_prefix - "webhook_" - end - - def events=(events) - events = Array(events).map { |event| event.to_s.underscore } - super(Webhook::Event::EVENT_TYPES & events) - end - end -end diff --git a/app/models/webhook/event.rb b/app/models/webhook/event.rb deleted file mode 100644 index 74baf9182..000000000 --- a/app/models/webhook/event.rb +++ /dev/null @@ -1,28 +0,0 @@ -module Webhook - class Event - EVENT_TYPES = %w[ - article_created - article_updated - article_destroyed - ].freeze - - attr_reader :event_type, :payload, :timestamp, :event_id - - def initialize(event_type:, payload: {}) - raise InvalidEvent unless EVENT_TYPES.include?(event_type) - - @event_type = event_type - @payload = payload - - now = Time.current - @timestamp = now.rfc3339 - @event_id = Secrets::Generator.sortable(now) - end - - def as_json(*_args) - Webhook::EventSerializer.new(self).serializable_hash - end - end - - class InvalidEvent < StandardError; end -end diff --git a/app/models/webhook_endpoint.rb b/app/models/webhook_endpoint.rb deleted file mode 100644 index 5e2712ccc..000000000 --- a/app/models/webhook_endpoint.rb +++ /dev/null @@ -1,7 +0,0 @@ -class WebhookEndpoint < ApplicationRecord - resourcify - # This class exists to take advantage of Rolify for limiting authorization - # on internal reports. - # NOTE: It is not backed by a database table and should not be expected to - # function like a traditional Rails model -end diff --git a/app/serializers/webhook/article_destroyed_serializer.rb b/app/serializers/webhook/article_destroyed_serializer.rb deleted file mode 100644 index 785180093..000000000 --- a/app/serializers/webhook/article_destroyed_serializer.rb +++ /dev/null @@ -1,6 +0,0 @@ -module Webhook - class ArticleDestroyedSerializer < ApplicationSerializer - set_type :article - attributes :title - end -end diff --git a/app/serializers/webhook/article_serializer.rb b/app/serializers/webhook/article_serializer.rb deleted file mode 100644 index 13fd9ed35..000000000 --- a/app/serializers/webhook/article_serializer.rb +++ /dev/null @@ -1,34 +0,0 @@ -module Webhook - class ArticleSerializer < ApplicationSerializer - set_type :article - attributes :title, :description, :readable_publish_date, :cached_tag_list, :cached_tag_list_array, - :slug, :path, :url, :comments_count, :public_reactions_count, :body_markdown - - attribute :canonical_url, &:processed_canonical_url - attribute :body_html, &:processed_html - attribute :created_at do |a| - a.created_at.utc.iso8601 - end - attribute :edited_at do |a| - a.edited_at&.utc&.iso8601 - end - attribute :crossposted_at do |a| - a.crossposted_at&.utc&.iso8601 - end - attribute :published_at do |a| - a.published_at&.utc&.iso8601 - end - attribute :last_comment_at do |a| - a.last_comment_at&.utc&.iso8601 - end - attribute :cover_image do |a| - CloudCoverUrl.new(a.url).call - end - attribute :social_image do |article| - Articles::SocialImage.new(article).url - end - attribute :user do |a| - UserSerializer.new(a.user).serializable_hash - end - end -end diff --git a/app/serializers/webhook/event_serializer.rb b/app/serializers/webhook/event_serializer.rb deleted file mode 100644 index 528e98e5c..000000000 --- a/app/serializers/webhook/event_serializer.rb +++ /dev/null @@ -1,8 +0,0 @@ -module Webhook - class EventSerializer < ApplicationSerializer - set_type :webhook_event - set_id :event_id - - attributes :event_type, :timestamp, :payload - end -end diff --git a/app/serializers/webhook/user_serializer.rb b/app/serializers/webhook/user_serializer.rb deleted file mode 100644 index e07ff9537..000000000 --- a/app/serializers/webhook/user_serializer.rb +++ /dev/null @@ -1,12 +0,0 @@ -module Webhook - class UserSerializer < ApplicationSerializer - attributes :name, :username, :twitter_username, :github_username - attribute :website_url, &:processed_website_url - attribute :profile_image do |user| - Images::Profile.call(user.profile_image_url, length: 640) - end - attribute :profile_image_90 do |user| - Images::Profile.call(user.profile_image_url, length: 90) - end - end -end diff --git a/app/services/articles/creator.rb b/app/services/articles/creator.rb index 593c28dcc..3728e4ead 100644 --- a/app/services/articles/creator.rb +++ b/app/services/articles/creator.rb @@ -1,9 +1,8 @@ module Articles class Creator - def initialize(user, article_params, event_dispatcher = Webhook::DispatchEvent) + def initialize(user, article_params) @user = user @article_params = article_params - @event_dispatcher = event_dispatcher end def self.call(...) @@ -22,7 +21,6 @@ module Articles # Send notifications to any mentioned users, followed by any users who follow the article's author. Notification.send_to_mentioned_users_and_followers(article) if article.published? - dispatch_event(article) end article @@ -30,7 +28,7 @@ module Articles private - attr_reader :user, :article_params, :event_dispatcher + attr_reader :user, :article_params def rate_limit! rate_limit_to_use = if user.decorate.considered_new? @@ -42,12 +40,6 @@ module Articles user.rate_limiter.check_limit!(rate_limit_to_use) end - def dispatch_event(article) - return unless article.published? - - event_dispatcher.call("article_created", article) - end - def save_article series = article_params[:series] tags = article_params[:tags] diff --git a/app/services/articles/destroyer.rb b/app/services/articles/destroyer.rb index 9dc18ef2e..1e764836a 100644 --- a/app/services/articles/destroyer.rb +++ b/app/services/articles/destroyer.rb @@ -2,7 +2,7 @@ module Articles module Destroyer module_function - def call(article, event_dispatcher = Webhook::DispatchEvent) + def call(article) # comments will automatically lose the connection to their article once `.destroy` is called, # due to the `dependent: nullify` clause, so to remove their notifications, # we need to cache the ids in advance @@ -15,8 +15,6 @@ module Articles if article_comments_ids.present? Notification.remove_all(notifiable_ids: article_comments_ids, notifiable_type: "Comment") end - - event_dispatcher.call("article_destroyed", article) if article.published? end end end diff --git a/app/services/articles/updater.rb b/app/services/articles/updater.rb index 0aab18361..61026503c 100644 --- a/app/services/articles/updater.rb +++ b/app/services/articles/updater.rb @@ -2,11 +2,10 @@ module Articles class Updater Result = Struct.new(:success, :article, keyword_init: true) - def initialize(user, article, article_params, event_dispatcher = Webhook::DispatchEvent) + def initialize(user, article, article_params) @user = user @article = article @article_params = article_params - @event_dispatcher = event_dispatcher end def self.call(...) @@ -16,9 +15,6 @@ module Articles def call user.rate_limiter.check_limit!(:article_update) - # Grab the state of the article's "publish" status before making any further updates to it. - was_previously_published = article.published - # updated edited time only if already published and not edited by an admin update_edited_at = article.user == user && article.published attrs = Articles::Attributes.new(article_params, article.user).for_update(update_edited_at: update_edited_at) @@ -52,19 +48,12 @@ module Articles notifiable_type: "Mention") end end - - # Do not notify if the article was previously already in a published state or is continually unpublished. - dispatch_event(article) if article.published || was_previously_published end Result.new(success: success, article: article.decorate) end private - attr_reader :user, :article, :article_params, :event_dispatcher - - def dispatch_event(article) - event_dispatcher.call("article_updated", article) - end + attr_reader :user, :article, :article_params end end diff --git a/app/services/secrets/generator.rb b/app/services/secrets/generator.rb deleted file mode 100644 index 80c9bf59a..000000000 --- a/app/services/secrets/generator.rb +++ /dev/null @@ -1,10 +0,0 @@ -module Secrets - module Generator - module_function - - # Generates a unique and lexicographically sortable ID - def sortable(time = Time.current) - ULID.generate(time) - end - end -end diff --git a/app/services/users/delete_activity.rb b/app/services/users/delete_activity.rb index 5be44e76f..9e05cf357 100644 --- a/app/services/users/delete_activity.rb +++ b/app/services/users/delete_activity.rb @@ -9,7 +9,6 @@ module Users user.created_podcasts.update_all(creator_id: nil) user.blocker_blocks.delete_all user.blocked_blocks.delete_all - user.webhook_endpoints.delete_all user.authored_notes.delete_all user.display_ad_events.delete_all user.email_messages.delete_all diff --git a/app/services/webhook/dispatch_event.rb b/app/services/webhook/dispatch_event.rb deleted file mode 100644 index 9c3fab000..000000000 --- a/app/services/webhook/dispatch_event.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Webhook - class DispatchEvent - def initialize(event_type, record) - @event_type = event_type - @record = record - end - - def self.call(...) - new(...).call - end - - def call - endpoint_urls = Endpoint.for_events([event_type]).where(user_id: record.user_id).pluck(:target_url) - return if endpoint_urls.empty? - - event_json = Event.new(event_type: event_type, payload: PayloadAdapter.new(record).hash).to_json - endpoint_urls.each do |url| - DispatchEventWorker.perform_async(url, event_json) - end - end - - private - - attr_reader :event_type, :record - end -end diff --git a/app/services/webhook/payload_adapter.rb b/app/services/webhook/payload_adapter.rb deleted file mode 100644 index 67dac74b4..000000000 --- a/app/services/webhook/payload_adapter.rb +++ /dev/null @@ -1,30 +0,0 @@ -module Webhook - class PayloadAdapter - def initialize(object) - raise InvalidPayloadObject unless object.is_a?(Article) - - @object = object - end - - def hash - serializer.new(prepared_object).serializable_hash - end - - private - - attr_reader :object - - # decorate article before serializing - def prepared_object - return object unless object.is_a?(Article) && !object.decorated? - - object.decorate - end - - def serializer - object.destroyed? ? ArticleDestroyedSerializer : ArticleSerializer - end - end - - class InvalidPayloadObject < StandardError; end -end diff --git a/app/views/api/v0/webhooks/index.json.jbuilder b/app/views/api/v0/webhooks/index.json.jbuilder deleted file mode 100644 index c82ac6a4e..000000000 --- a/app/views/api/v0/webhooks/index.json.jbuilder +++ /dev/null @@ -1,7 +0,0 @@ -json.array! @webhooks.each do |endpoint| - json.type_of "webhook_endpoint" - - json.extract!(endpoint, :id, :source, :target_url, :events) - - json.created_at endpoint.created_at.rfc3339 -end diff --git a/app/views/api/v0/webhooks/show.json.jbuilder b/app/views/api/v0/webhooks/show.json.jbuilder deleted file mode 100644 index 886e74650..000000000 --- a/app/views/api/v0/webhooks/show.json.jbuilder +++ /dev/null @@ -1,7 +0,0 @@ -json.type_of "webhook_endpoint" - -json.call(@webhook, :id, :source, :target_url, :events) - -json.created_at @webhook.created_at.rfc3339 - -json.partial! "api/v0/shared/user", user: @webhook.user diff --git a/app/workers/webhook/destroy_worker.rb b/app/workers/webhook/destroy_worker.rb deleted file mode 100644 index c37d27915..000000000 --- a/app/workers/webhook/destroy_worker.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Webhook - class DestroyWorker - include Sidekiq::Worker - - sidekiq_options queue: :low_priority, retry: 10 - - def perform(user_id, application_id) - Webhook::Endpoint.destroy_by(user_id: user_id, oauth_application_id: application_id) - end - end -end diff --git a/app/workers/webhook/dispatch_event_worker.rb b/app/workers/webhook/dispatch_event_worker.rb deleted file mode 100644 index 1321b4241..000000000 --- a/app/workers/webhook/dispatch_event_worker.rb +++ /dev/null @@ -1,12 +0,0 @@ -module Webhook - class DispatchEventWorker - include Sidekiq::Worker - - sidekiq_options queue: :medium_priority - - def perform(endpoint_url, event_json) - uri = Addressable::URI.parse(endpoint_url) - HTTParty.post(uri, headers: { "Content-Type" => "application/json" }, body: event_json, timeout: 10) - end - end -end diff --git a/config/routes.rb b/config/routes.rb index 46cccd218..de04875fa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -64,7 +64,6 @@ Rails.application.routes.draw do get :organizations end resources :readinglist, only: [:index] - resources :webhooks, only: %i[index create show destroy] resources :listings, only: %i[index show create update] get "/listings/category/:category", to: "listings#index", as: :listings_category diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 033f3bdbd..9ac0eedfa 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -143,7 +143,6 @@ namespace :admin do post "bust_cache" end end - resources :webhook_endpoints, only: :index # We do not expose the Data Update Scripts to all Forems by default. constraints(->(_request) { FeatureFlag.enabled?(:data_update_scripts) }) do diff --git a/spec/factories/webhook_endpoints.rb b/spec/factories/webhook_endpoints.rb deleted file mode 100644 index 06ce91bc2..000000000 --- a/spec/factories/webhook_endpoints.rb +++ /dev/null @@ -1,8 +0,0 @@ -FactoryBot.define do - factory :webhook_endpoint, class: "Webhook::Endpoint" do - target_url { Faker::Internet.url(scheme: "https") } - events { Webhook::Event::EVENT_TYPES } - user - source { "stackbit" } - end -end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 5010a400d..76dd00371 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -110,7 +110,6 @@ RSpec.describe User, type: :model do it { is_expected.to have_many(:subscribed_to_user_subscriptions).dependent(:destroy) } it { is_expected.to have_many(:subscribers).dependent(:destroy) } it { is_expected.to have_many(:tweets).dependent(:nullify) } - it { is_expected.to have_many(:webhook_endpoints).class_name("Webhook::Endpoint").dependent(:delete_all) } # rubocop:disable RSpec/NamedSubject it do diff --git a/spec/models/webhook/endpoint_spec.rb b/spec/models/webhook/endpoint_spec.rb deleted file mode 100644 index 5570e662e..000000000 --- a/spec/models/webhook/endpoint_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -require "rails_helper" - -RSpec.describe Webhook::Endpoint, type: :model do - let!(:endpoint) do - create( - :webhook_endpoint, user: user, events: %w[article_created article_updated article_destroyed] - ) - end - let(:user) { create(:user) } - - describe "validations" do - it { is_expected.to belong_to(:user).inverse_of(:webhook_endpoints) } - - it { is_expected.to validate_presence_of(:events) } - it { is_expected.to validate_presence_of(:source) } - it { is_expected.to validate_presence_of(:target_url) } - it { is_expected.to validate_presence_of(:user_id) } - - it { is_expected.to validate_uniqueness_of(:target_url) } - - it { is_expected.to allow_value("https://foo.com").for(:target_url) } - it { is_expected.not_to allow_value("http://foo.com").for(:target_url) } - end - - it "is valid" do - expect(endpoint).to be_valid - end - - it "sets events according to the list" do - webhook = create( - :webhook_endpoint, events: %w[article_updated other_updated cool article_created] - ) - webhook.reload - expect(webhook.events.sort).to eq(%w[article_created article_updated]) - end - - context "when endpoints exist" do - let!(:epoint2) { create(:webhook_endpoint, events: %w[article_destroyed], user: user) } - let!(:epoint3) { create(:webhook_endpoint, events: %w[article_updated article_destroyed]) } - - before do - create(:webhook_endpoint, events: %w[article_created]) - end - - it "finds for events" do - d_points = described_class.for_events("article_destroyed") - expect(d_points.ids.sort).to eq([endpoint, epoint2, epoint3].map(&:id).sort) - end - - it "finds for_events array" do - endpoints = described_class.for_events(%w[article_created article_destroyed]) - expect(endpoints.ids).to eq([endpoint.id]) - end - - it "belongs to user" do - expect(user.webhook_endpoints.ids.sort).to eq([endpoint, epoint2].map(&:id).sort) - end - end -end diff --git a/spec/models/webhook/event_spec.rb b/spec/models/webhook/event_spec.rb deleted file mode 100644 index 5126681bc..000000000 --- a/spec/models/webhook/event_spec.rb +++ /dev/null @@ -1,60 +0,0 @@ -require "rails_helper" - -RSpec.describe Webhook::Event, type: :model do - let(:article) { create(:article) } - let(:payload) { Webhook::PayloadAdapter.new(article).hash } - - describe "validations" do - it "raises an exception with a unknown event type" do - expect do - described_class.new(event_type: "cool_event") - end.to raise_error(Webhook::InvalidEvent) - end - end - - describe "#as_json" do - it "provides correct json (sample)" do - event = described_class.new(event_type: "article_created", payload: { title: "Hello, world" }) - hash = event.as_json - - attributes = hash[:data][:attributes] - - expect(attributes[:event_type]).to eq("article_created") - expect(attributes[:timestamp]).to be_truthy - expect(attributes[:payload][:title]).to eq("Hello, world") - end - - it "provides correct json including article" do - event = described_class.new(event_type: "article_updated", payload: payload) - hash = event.as_json - attributes = hash[:data][:attributes] - expect(attributes[:event_type]).to eq("article_updated") - expect(attributes[:payload][:data][:attributes][:title]).to eq(article.title) - end - - it "provides an event_id dependent on time" do - event1 = described_class.new(event_type: "article_updated", payload: payload) - event1_id = event1.as_json.dig(:data, :id) - - event2 = nil - Timecop.freeze(1.month.ago) do - event2 = described_class.new(event_type: "article_updated", payload: payload) - end - event2_id = event2.as_json.dig(:data, :id) - - expect(event2_id < event1_id).to be(true) - end - end - - describe "#to_json" do - it "provides correct json including article" do - event = described_class.new(event_type: "article_updated", payload: payload) - json = event.to_json - hash = JSON.parse(json) - attributes = hash["data"]["attributes"] - - expect(attributes["event_type"]).to eq("article_updated") - expect(attributes["payload"]["data"]["attributes"]["title"]).to eq(article.title) - end - end -end diff --git a/spec/requests/api/v0/webhooks_spec.rb b/spec/requests/api/v0/webhooks_spec.rb deleted file mode 100644 index 6eb3e8bbc..000000000 --- a/spec/requests/api/v0/webhooks_spec.rb +++ /dev/null @@ -1,173 +0,0 @@ -require "rails_helper" - -RSpec.describe "Api::V0::Webhooks", type: :request do - let(:user) { create(:user) } - let!(:webhook) do - create(:webhook_endpoint, user: user, target_url: "https://api.example.com/go") - end - - describe "GET /api/v0/webhooks" do - let(:webhook2) do - create(:webhook_endpoint, user: user, target_url: "https://api.example.com/webhook") - end - - context "when accessing via cookie" do - before do - sign_in user - create(:webhook_endpoint) - end - - it "returns 200 on success" do - get api_webhooks_path - expect(response).to have_http_status(:ok) - end - - it "returns json on success" do - webhook2 - get api_webhooks_path - - expect(response.parsed_body).to include( - hash_including("id" => webhook.id, "target_url" => webhook.target_url), - hash_including("id" => webhook2.id, "target_url" => webhook2.target_url), - ) - end - - it "returns the correct json representation" do - get api_webhooks_path - - expect(response.parsed_body.first).to eq( - "created_at" => webhook.created_at.rfc3339, - "events" => webhook.events, - "id" => webhook.id, - "source" => webhook.source, - "target_url" => webhook.target_url, - "type_of" => "webhook_endpoint", - ) - end - end - end - - describe "GET /api/v0/webhooks/:id" do - before do - sign_in user - end - - it "returns 200 on success" do - get api_webhook_path(webhook.id) - - expect(response).to have_http_status(:ok) - end - - it "returns 404 if the webhook does not exist" do - get api_webhook_path(9999) - - expect(response).to have_http_status(:not_found) - end - - it "returns 404 if another user webhook is accessed" do - other_webhook = create(:webhook_endpoint, user: create(:user)) - - get api_webhook_path(other_webhook.id) - - expect(response).to have_http_status(:not_found) - end - - it "returns the correct json representation" do - get api_webhook_path(webhook.id) - - expect(response.parsed_body).to include( - "created_at" => webhook.created_at.rfc3339, - "events" => webhook.events, - "id" => webhook.id, - "source" => webhook.source, - "target_url" => webhook.target_url, - "type_of" => "webhook_endpoint", - ) - end - - it "returns the correct json representation for the webhook user" do - get api_webhook_path(webhook.id) - - response_webhook_user = response.parsed_body["user"] - - expect(response_webhook_user).to eq({ - "name" => webhook.user.name, - "username" => webhook.user.username, - "twitter_username" => webhook.user.twitter_username, - "github_username" => webhook.user.github_username, - "website_url" => webhook.user.processed_website_url, - "profile_image" => Images::Profile.call(webhook.user.profile_image_url, - length: 640), - "profile_image_90" => Images::Profile.call(webhook.user.profile_image_url, - length: 90) - }) - end - end - - describe "POST /api/v0/webhooks" do - let(:webhook_params) do - { - source: "stackbit", - target_url: Faker::Internet.url(scheme: "https"), - events: %w[article_created article_updated article_destroyed] - } - end - - before do - sign_in user - end - - it "creates a webhook" do - expect do - post api_webhooks_path, params: { webhook_endpoint: webhook_params } - end.to change(Webhook::Endpoint, :count).by(1) - end - - it "creates a webhook with events and data" do - post api_webhooks_path, params: { webhook_endpoint: webhook_params } - created_webhook = user.webhook_endpoints.last - expect(created_webhook.events).to eq(%w[article_created article_updated article_destroyed]) - expect(created_webhook.target_url).to eq(webhook_params[:target_url]) - expect(created_webhook.source).to eq(webhook_params[:source]) - expect(created_webhook.oauth_application_id).to eq(nil) - end - - it "returns :created and json response on success" do - post api_webhooks_path, params: { webhook_endpoint: webhook_params } - expect(response).to have_http_status(:created) - expect(response.media_type).to eq("application/json") - json = JSON.parse(response.body) - expect(json["target_url"]).to eq(webhook_params[:target_url]) - end - end - - describe "DELETE /api/v0/webhooks/:id" do - before do - sign_in user - end - - it "deletes the webhook" do - expect do - delete api_webhook_path(webhook.id) - end.to change(Webhook::Endpoint, :count).by(-1) - end - - it "returns 204 on success" do - delete api_webhook_path(webhook.id) - expect(response).to have_http_status(:no_content) - end - - it "doesn't allow to destroy other user webhook" do - other_webhook = create(:webhook_endpoint, user: create(:user)) - expect do - delete api_webhook_path(other_webhook.id) - end.not_to change(Webhook::Endpoint, :count) - end - - it "returns 404 if another user webhook is accessed" do - other_webhook = create(:webhook_endpoint, user: create(:user)) - delete api_webhook_path(other_webhook.id) - expect(response).to have_http_status(:not_found) - end - end -end diff --git a/spec/requests/articles/articles_create_spec.rb b/spec/requests/articles/articles_create_spec.rb index bdf381d5e..918a6f563 100644 --- a/spec/requests/articles/articles_create_spec.rb +++ b/spec/requests/articles/articles_create_spec.rb @@ -77,24 +77,6 @@ RSpec.describe "ArticlesCreate", type: :request do } end - before do - create(:webhook_endpoint, events: %w[article_created article_updated], target_url: url, user: user) - end - - it "doesn't schedule a dispatching event job (unpublished)" do - sidekiq_assert_no_enqueued_jobs(only: Webhook::DispatchEventWorker) do - post "/articles", params: article_params - end - end - - it "schedules a dispatching event job (published)" do - body_markdown = "---\ntitle: hey hey hahuu\npublished: true\nseries: helloyo\n---\nYo ho ho#{rand(100)}" - article_params[:article][:body_markdown] = body_markdown - sidekiq_assert_enqueued_jobs(1, only: Webhook::DispatchEventWorker) do - post "/articles", params: article_params - end - end - it "doesn't fail when executing jobs" do stub_request(:post, url).to_return(status: 200) sidekiq_perform_enqueued_jobs do diff --git a/spec/requests/articles/articles_update_spec.rb b/spec/requests/articles/articles_update_spec.rb index 551c90db4..14d076612 100644 --- a/spec/requests/articles/articles_update_spec.rb +++ b/spec/requests/articles/articles_update_spec.rb @@ -167,13 +167,4 @@ RSpec.describe "ArticlesUpdate", type: :request do expect(response).to redirect_to "#{article.path}/edit" expect(article.reload.video_thumbnail_url).to include "https://i.imgur.com/HPiu7N4.jpg" end - - it "schedules a dispatching event job" do - create(:webhook_endpoint, events: %w[article_created article_updated], user: user) - sidekiq_assert_enqueued_jobs(1, only: Webhook::DispatchEventWorker) do - put "/articles/#{article.id}", params: { - article: { title: "new_title", body_markdown: "Yo ho ho#{rand(100)}", tag_list: "yo" } - } - end - end end diff --git a/spec/services/articles/creator_spec.rb b/spec/services/articles/creator_spec.rb index b21ddaeeb..e164af68d 100644 --- a/spec/services/articles/creator_spec.rb +++ b/spec/services/articles/creator_spec.rb @@ -38,21 +38,6 @@ RSpec.describe Articles::Creator, type: :service do described_class.call(user, valid_attributes) end.to change(NotificationSubscription, :count).by(1) end - - it "calls an event dispatcher" do - event_dispatcher = double - allow(event_dispatcher).to receive(:call) - article = described_class.call(user, valid_attributes, event_dispatcher) - expect(event_dispatcher).to have_received(:call).with("article_created", article) - end - - it "doesn't call an event dispatcher when an article is unpublished" do - attributes = attributes_for(:article, published: false) - event_dispatcher = double - allow(event_dispatcher).to receive(:call) - article = described_class.call(user, attributes, event_dispatcher) - expect(event_dispatcher).not_to have_received(:call).with("article_created", article) - end end context "when invalid attributes" do @@ -94,12 +79,5 @@ RSpec.describe Articles::Creator, type: :service do described_class.call(user, invalid_body_attributes) end.not_to change(NotificationSubscription, :count) end - - it "doesn't call an event dispatcher" do - event_dispatcher = double - allow(event_dispatcher).to receive(:call) - described_class.call(user, invalid_body_attributes, event_dispatcher) - expect(event_dispatcher).not_to have_received(:call) - end end end diff --git a/spec/services/articles/destroyer_spec.rb b/spec/services/articles/destroyer_spec.rb index e014e4f03..9720d4d0f 100644 --- a/spec/services/articles/destroyer_spec.rb +++ b/spec/services/articles/destroyer_spec.rb @@ -4,10 +4,6 @@ RSpec.describe Articles::Destroyer, type: :service do let(:article) { create(:article) } let(:event_dispatcher) { double } - before do - allow(event_dispatcher).to receive(:call) - end - it "destroys an article" do described_class.call(article) expect(Article.find_by(id: article.id)).to be_nil @@ -19,15 +15,4 @@ RSpec.describe Articles::Destroyer, type: :service do described_class.call(article) end end - - it "calls events dispatcher" do - described_class.call(article, event_dispatcher) - expect(event_dispatcher).to have_received(:call).with("article_destroyed", article) - end - - it "doesn't call a dispatched when a draft is destroyed" do - draft = create(:article, published: false) - described_class.call(draft, event_dispatcher) - expect(event_dispatcher).not_to have_received(:call) - end end diff --git a/spec/services/articles/updater_spec.rb b/spec/services/articles/updater_spec.rb index e1b34b80c..fd1aea54c 100644 --- a/spec/services/articles/updater_spec.rb +++ b/spec/services/articles/updater_spec.rb @@ -138,34 +138,4 @@ RSpec.describe Articles::Updater, type: :service do end end end - - describe "events dispatcher" do - let(:event_dispatcher) { double } - - before do - allow(event_dispatcher).to receive(:call) - end - - it "calls the dispatcher" do - described_class.call(user, article, attributes, event_dispatcher) - expect(event_dispatcher).to have_received(:call).with("article_updated", article) - end - - it "doesn't call the dispatcher when unpublished => unpublished" do - described_class.call(user, draft, attributes, event_dispatcher) - expect(event_dispatcher).not_to have_received(:call) - end - - it "calls the dispatcher when unpublished => published" do - attributes[:published] = true - described_class.call(user, draft, attributes, event_dispatcher) - expect(event_dispatcher).to have_received(:call).with("article_updated", draft) - end - - it "calls the dispatcher when published => unpublished" do - attributes[:published] = false - described_class.call(user, article, attributes, event_dispatcher) - expect(event_dispatcher).to have_received(:call).with("article_updated", article) - end - end end diff --git a/spec/services/secrets/generator_spec.rb b/spec/services/secrets/generator_spec.rb deleted file mode 100644 index d441d1b52..000000000 --- a/spec/services/secrets/generator_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require "rails_helper" - -RSpec.describe Secrets::Generator, type: :service do - describe ".sortable" do - it "generates unique identifiers" do - expect(described_class.sortable).not_to eq(described_class.sortable) - end - - it "generates sortable identifiers" do - now_id = described_class.sortable - a_month_from_now_id = described_class.sortable(1.month.from_now) - a_month_ago_id = described_class.sortable(1.month.ago) - - expected_ids = [a_month_ago_id, now_id, a_month_from_now_id] - expect([now_id, a_month_from_now_id, a_month_ago_id].sort).to eq(expected_ids) - end - end -end diff --git a/spec/services/webhook/dispatch_event_spec.rb b/spec/services/webhook/dispatch_event_spec.rb deleted file mode 100644 index 3f0d32941..000000000 --- a/spec/services/webhook/dispatch_event_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require "rails_helper" - -RSpec.describe Webhook::DispatchEvent, type: :service do - let!(:user) { create(:user) } - let!(:article) { create(:article, user: user) } - - it "does nothing if there are no corresponding endpoints" do - create(:webhook_endpoint, events: %w[article_created], user: user) - sidekiq_assert_no_enqueued_jobs(only: Webhook::DispatchEventWorker) do - described_class.call("article_destroyed", article) - end - end - - it "schedules jobs" do - create(:webhook_endpoint, events: %w[article_created], user: user, - target_url: "https://create-webhooks.example.com/accept") - create(:webhook_endpoint, events: %w[article_created article_updated article_destroyed], user: user, - target_url: "https://all-webhooks.example.com/accept") - create(:webhook_endpoint, events: %w[article_destroyed], user: user, - target_url: "https://destroy-webhooks.example.com/accept") - sidekiq_assert_enqueued_jobs(2, only: Webhook::DispatchEventWorker) do - described_class.call("article_created", article) - end - end - - it "doesn't schedule jobs if the endpoints belong to another user" do - user2 = create(:user) - create(:webhook_endpoint, events: %w[article_created], user: user2, - target_url: "https://create-webhooks.example.com/accept") - sidekiq_assert_no_enqueued_jobs(only: Webhook::DispatchEventWorker) do - described_class.call("article_created", article) - end - end -end diff --git a/spec/services/webhook/payload_adapter_spec.rb b/spec/services/webhook/payload_adapter_spec.rb deleted file mode 100644 index 709f8ccad..000000000 --- a/spec/services/webhook/payload_adapter_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -require "rails_helper" - -RSpec.describe Webhook::PayloadAdapter, type: :service do - it "raises an exception when invalid object is passed" do - expect do - described_class.new(User.new).hash - end.to raise_error(Webhook::InvalidPayloadObject) - end - - describe "#hash" do - let(:user) { create(:user) } - let!(:article) { create(:article, title: "I'm super", user: user) } - - it "returns a hash for a persisted article" do - data = described_class.new(article).hash - expect(data).to be_kind_of(Hash) - expect(data[:data][:attributes][:title]).to eq(article.title) - expect(data[:data][:attributes][:body_markdown]).to be_truthy - end - - it "returns a hash with a user" do - data = described_class.new(article).hash - expect(data[:data][:attributes][:user][:data][:attributes][:username]).to eq(user.username) - end - - it "returns a hash for a destroyed article" do - article = create(:article, title: "hello") - article.destroy - data = described_class.new(article).hash - expect(data).to be_kind_of(Hash) - expect(data[:data][:attributes][:title]).to eq("hello") - expect(data[:data][:attributes][:body_markdown]).to be_falsey - end - end -end diff --git a/spec/workers/webhook/destroy_worker_spec.rb b/spec/workers/webhook/destroy_worker_spec.rb deleted file mode 100644 index 42f1d3027..000000000 --- a/spec/workers/webhook/destroy_worker_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require "rails_helper" - -RSpec.describe Webhook::DestroyWorker, type: :worker do - let(:user) { create(:user) } - let!(:webhook_endpoint) { create(:webhook_endpoint, user: user) } - let!(:other_webhook_endpoint) { create(:webhook_endpoint) } - let(:worker) { subject } - - describe "#perform_now" do - xit "destroys webhook by user_id and app_id" do - worker.perform(user.id) - expect(Webhook::Endpoint.find_by(id: webhook_endpoint.id)).to be_nil - expect(other_webhook_endpoint.reload).to be_present - end - end -end diff --git a/spec/workers/webhook/dispatch_event_worker_spec.rb b/spec/workers/webhook/dispatch_event_worker_spec.rb deleted file mode 100644 index d76f8e3d5..000000000 --- a/spec/workers/webhook/dispatch_event_worker_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -require "rails_helper" - -RSpec.describe Webhook::DispatchEventWorker, type: :worker do - let(:worker) { subject } - - include_examples "#enqueues_on_correct_queue", "medium_priority", ["https://example.com", {}.to_json] - - describe "#perform_now" do - let(:article) { create(:article) } - let(:payload) { Webhook::PayloadAdapter.new(article).hash } - let(:json) { Webhook::Event.new(event_type: "article_updated", payload: payload).to_json } - let(:url) { Faker::Internet.url } - let(:client) { HTTParty } - - it "posts an event" do - allow(client).to receive(:post) - worker.perform(url, json) - expect(client).to have_received(:post).once - .with(Addressable::URI.parse(url), headers: { "Content-Type" => "application/json" }, - body: json, - timeout: 10) - end - - it "doesn't fail" do - stub_request(:post, url).to_return(status: 200) - worker.perform(url, json) - end - end -end diff --git a/vendor/cache/ulid-1.3.0.gem b/vendor/cache/ulid-1.3.0.gem deleted file mode 100644 index 43fd6d62b..000000000 Binary files a/vendor/cache/ulid-1.3.0.gem and /dev/null differ