discourse_subscription_client 0.1.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +10 -0
- data/app/assets/config/discourse_subscription_client_manifest.js +1 -0
- data/app/assets/stylesheets/discourse_subscription_client/application.css +15 -0
- data/app/controllers/discourse_subscription_client/admin_controller.rb +46 -0
- data/app/controllers/discourse_subscription_client/notices_controller.rb +82 -0
- data/app/controllers/discourse_subscription_client/subscriptions_controller.rb +17 -0
- data/app/controllers/discourse_subscription_client/suppliers_controller.rb +62 -0
- data/app/jobs/regular/discourse_subscription_client/find_resources.rb +9 -0
- data/app/jobs/scheduled/discourse_subscription_client/update_notices.rb +11 -0
- data/app/jobs/scheduled/discourse_subscription_client/update_subscriptions.rb +11 -0
- data/app/models/subscription_client_notice.rb +223 -0
- data/app/models/subscription_client_request.rb +4 -0
- data/app/models/subscription_client_resource.rb +27 -0
- data/app/models/subscription_client_subscription.rb +54 -0
- data/app/models/subscription_client_supplier.rb +54 -0
- data/app/serializers/discourse_subscription_client/notice_serializer.rb +52 -0
- data/app/serializers/discourse_subscription_client/resource_serializer.rb +8 -0
- data/app/serializers/discourse_subscription_client/subscription_serializer.rb +12 -0
- data/app/serializers/discourse_subscription_client/supplier_serializer.rb +16 -0
- data/config/locales/server.en.yml +40 -0
- data/config/routes.rb +19 -0
- data/config/settings.yml +16 -0
- data/db/migrate/20220318160955_create_subscription_client_suppliers.rb +17 -0
- data/db/migrate/20220318181029_create_subscription_client_resources.rb +14 -0
- data/db/migrate/20220318181054_create_subscription_client_notices.rb +22 -0
- data/db/migrate/20220318181140_create_subscription_client_subscriptions.rb +19 -0
- data/db/migrate/20230220130259_create_subscription_client_requests.rb +16 -0
- data/lib/discourse_subscription_client/authorization.rb +100 -0
- data/lib/discourse_subscription_client/engine.rb +97 -0
- data/lib/discourse_subscription_client/notices.rb +154 -0
- data/lib/discourse_subscription_client/request.rb +136 -0
- data/lib/discourse_subscription_client/resources.rb +97 -0
- data/lib/discourse_subscription_client/subscriptions/result.rb +118 -0
- data/lib/discourse_subscription_client/subscriptions.rb +93 -0
- data/lib/discourse_subscription_client/version.rb +5 -0
- data/lib/discourse_subscription_client.rb +8 -0
- metadata +252 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class SubscriptionClientSupplier < ActiveRecord::Base
|
4
|
+
has_many :resources, foreign_key: "supplier_id", class_name: "SubscriptionClientResource", dependent: :destroy
|
5
|
+
has_many :subscriptions, through: :resources
|
6
|
+
has_many :notices, class_name: "SubscriptionClientNotice", as: :notice_subject, dependent: :destroy
|
7
|
+
|
8
|
+
belongs_to :user
|
9
|
+
|
10
|
+
scope :authorized, -> { where("api_key IS NOT NULL") }
|
11
|
+
|
12
|
+
def destroy_authorization
|
13
|
+
if DiscourseSubscriptionClient::Authorization.revoke(self)
|
14
|
+
update(api_key: nil, user_id: nil, authorized_at: nil)
|
15
|
+
deactivate_all_subscriptions!
|
16
|
+
true
|
17
|
+
else
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def authorized?
|
23
|
+
api_key.present?
|
24
|
+
end
|
25
|
+
|
26
|
+
def deactivate_all_subscriptions!
|
27
|
+
subscriptions.update_all(subscribed: false)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.publish_authorized_supplier_count
|
31
|
+
payload = { authorized_supplier_count: authorized.count }
|
32
|
+
group_id_key = SiteSetting.subscription_client_allow_moderator_subscription_management ? :staff : :admins
|
33
|
+
MessageBus.publish("/subscription_client", payload, group_ids: [Group::AUTO_GROUPS[group_id_key.to_sym]])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# == Schema Information
|
38
|
+
#
|
39
|
+
# Table name: subscription_client_suppliers
|
40
|
+
#
|
41
|
+
# id :bigint not null, primary key
|
42
|
+
# name :string
|
43
|
+
# url :string not null
|
44
|
+
# api_key :string
|
45
|
+
# user_id :bigint
|
46
|
+
# authorized_at :datetime
|
47
|
+
# created_at :datetime not null
|
48
|
+
# updated_at :datetime not null
|
49
|
+
#
|
50
|
+
# Indexes
|
51
|
+
#
|
52
|
+
# index_subscription_client_suppliers_on_url (url) UNIQUE
|
53
|
+
# index_subscription_client_suppliers_on_user_id (user_id)
|
54
|
+
#
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DiscourseSubscriptionClient
|
4
|
+
class NoticeSerializer < ApplicationSerializer
|
5
|
+
attributes :id,
|
6
|
+
:title,
|
7
|
+
:message,
|
8
|
+
:notice_type,
|
9
|
+
:notice_subject_type,
|
10
|
+
:notice_subject_id,
|
11
|
+
:plugin_status_resource,
|
12
|
+
:created_at,
|
13
|
+
:expired_at,
|
14
|
+
:updated_at,
|
15
|
+
:dismissed_at,
|
16
|
+
:retrieved_at,
|
17
|
+
:hidden_at,
|
18
|
+
:dismissable,
|
19
|
+
:can_hide
|
20
|
+
|
21
|
+
has_one :supplier, serializer: DiscourseSubscriptionClient::SupplierSerializer, embed: :objects
|
22
|
+
has_one :resource, serializer: DiscourseSubscriptionClient::ResourceSerializer, embed: :objects
|
23
|
+
|
24
|
+
def include_supplier?
|
25
|
+
object.supplier.present?
|
26
|
+
end
|
27
|
+
|
28
|
+
def include_resource?
|
29
|
+
object.resource.present?
|
30
|
+
end
|
31
|
+
|
32
|
+
def plugin_status_resource
|
33
|
+
object.plugin_status_resource?
|
34
|
+
end
|
35
|
+
|
36
|
+
def dismissable
|
37
|
+
object.dismissable?
|
38
|
+
end
|
39
|
+
|
40
|
+
def can_hide
|
41
|
+
object.can_hide?
|
42
|
+
end
|
43
|
+
|
44
|
+
def notice_type
|
45
|
+
SubscriptionClientNotice.types.key(object.notice_type)
|
46
|
+
end
|
47
|
+
|
48
|
+
def messsage
|
49
|
+
PrettyText.cook(object.message)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DiscourseSubscriptionClient
|
4
|
+
class SupplierSerializer < ApplicationSerializer
|
5
|
+
attributes :id,
|
6
|
+
:name,
|
7
|
+
:authorized,
|
8
|
+
:authorized_at
|
9
|
+
|
10
|
+
has_one :user, serializer: BasicUserSerializer, embed: :objects
|
11
|
+
|
12
|
+
def authorized
|
13
|
+
object.api_key.present? && object.authorized_at.present?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
en:
|
2
|
+
subscription_client:
|
3
|
+
custom_title: "Subscription"
|
4
|
+
|
5
|
+
notices:
|
6
|
+
connection_error: "Failed to connect to %{url}"
|
7
|
+
compatibility_issue:
|
8
|
+
title: The %{resource} is incompatibile with the latest version of Discourse.
|
9
|
+
message: Please check the %{resource} status before updating Discourse.
|
10
|
+
resource:
|
11
|
+
connection_error:
|
12
|
+
title: Unable to connect to the plugin status server
|
13
|
+
message: Please check your plugins' compatibility with the latest version of Discourse before updating.
|
14
|
+
supplier:
|
15
|
+
connection_error:
|
16
|
+
title: Unable to connect to %{supplier}
|
17
|
+
message: Any subscriptions you have with %{supplier} have been deactivated.
|
18
|
+
|
19
|
+
subscriptions:
|
20
|
+
error:
|
21
|
+
supplier_connection: Failed to connect to %{supplier} (%{supplier_url})
|
22
|
+
invalid_response: Invalid response from %{supplier} (%{supplier_url})
|
23
|
+
info:
|
24
|
+
no_suppliers: "Update was not run as there are no authorized suppliers."
|
25
|
+
no_subscriptions: "There are no active subscriptions for %{supplier} (%{supplier_url})."
|
26
|
+
no_resource: "There is no %{resource} resource associated with %{supplier} (%{supplier_url})."
|
27
|
+
deactivated_subscription: "Deactivated subscription for %{resource_name} (%{resource_id})."
|
28
|
+
updated_subscription: "Updated active subscription for %{resource_name} (%{resource_id}). Product: (%{product_id}), Price: %{price}, Supplier: %{supplier}"
|
29
|
+
created_subscription: "Created active subscription for resource: %{resource-id}. Product: %{product_id}; Price: %{price}, Supplier: %{supplier}"
|
30
|
+
failed_to_create_subscription: "Failed to create subscription for %{resource_name} (%{resource-id}). product: %{product_id}; price: %{price}. From %{supplier} (%{supplier_url})"
|
31
|
+
|
32
|
+
site_settings:
|
33
|
+
subscription_client_enabled: "Enable the subscription client."
|
34
|
+
subscription_client_verbose_logs: "Enable verbose logs for the subscription client."
|
35
|
+
subscription_client_warning_notices_on_dashboard: "Show warning notices about subscriptions on the admin dashboard."
|
36
|
+
subscription_client_allow_moderator_subscription_management: "Allow moderators to manage subscriptions."
|
37
|
+
subscription_client_allow_moderator_supplier_management: "Allow moderators to manage subscription suppliers."
|
38
|
+
subscription_client_request_plugin_statuses: Request the status of plugins from discourse.pluginmanager.org.
|
39
|
+
errors:
|
40
|
+
allow_moderator_subscription_management_not_enabled: "Allow moderator subscription management must be enabled."
|
data/config/routes.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
DiscourseSubscriptionClient::Engine.routes.draw do
|
4
|
+
get "" => "admin#index"
|
5
|
+
get ".json" => "admin#index"
|
6
|
+
|
7
|
+
get "suppliers" => "suppliers#index"
|
8
|
+
get "suppliers/authorize" => "suppliers#authorize"
|
9
|
+
get "suppliers/authorize/callback" => "suppliers#authorize_callback"
|
10
|
+
delete "suppliers/authorize" => "suppliers#destroy"
|
11
|
+
|
12
|
+
get "subscriptions" => "subscriptions#index"
|
13
|
+
post "subscriptions" => "subscriptions#update"
|
14
|
+
|
15
|
+
get "notices" => "notices#index"
|
16
|
+
put "notices/:notice_id/dismiss" => "notices#dismiss"
|
17
|
+
put "notices/:notice_id/hide" => "notices#hide"
|
18
|
+
put "notices/:notice_id/show" => "notices#show"
|
19
|
+
end
|
data/config/settings.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
plugins:
|
2
|
+
subscription_client_enabled:
|
3
|
+
default: true
|
4
|
+
client: true
|
5
|
+
subscription_client_verbose_logs:
|
6
|
+
default: false
|
7
|
+
subscription_client_warning_notices_on_dashboard:
|
8
|
+
default: true
|
9
|
+
subscription_client_allow_moderator_subscription_management:
|
10
|
+
default: false
|
11
|
+
subscription_client_allow_moderator_supplier_management:
|
12
|
+
default: false
|
13
|
+
client: true
|
14
|
+
subscription_client_request_plugin_statuses:
|
15
|
+
default: false
|
16
|
+
hidden: true
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateSubscriptionClientSuppliers < ActiveRecord::Migration[6.1]
|
4
|
+
def change
|
5
|
+
create_table :subscription_client_suppliers do |t|
|
6
|
+
t.string :name
|
7
|
+
t.string :url, null: false
|
8
|
+
t.string :api_key
|
9
|
+
t.references :user
|
10
|
+
t.datetime :authorized_at
|
11
|
+
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
|
15
|
+
add_index :subscription_client_suppliers, [:url], unique: true
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateSubscriptionClientResources < ActiveRecord::Migration[6.1]
|
4
|
+
def change
|
5
|
+
create_table :subscription_client_resources do |t|
|
6
|
+
t.references :supplier, foreign_key: { to_table: :subscription_client_suppliers }
|
7
|
+
t.string :name, null: false
|
8
|
+
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
|
12
|
+
add_index :subscription_client_resources, %i[supplier_id name], unique: true
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateSubscriptionClientNotices < ActiveRecord::Migration[6.1]
|
4
|
+
def change
|
5
|
+
create_table :subscription_client_notices do |t|
|
6
|
+
t.string :title, null: false
|
7
|
+
t.string :message
|
8
|
+
t.integer :notice_type, null: false
|
9
|
+
t.references :notice_subject, polymorphic: true
|
10
|
+
t.datetime :changed_at
|
11
|
+
t.datetime :retrieved_at
|
12
|
+
t.datetime :dismissed_at
|
13
|
+
t.datetime :expired_at
|
14
|
+
t.datetime :hidden_at
|
15
|
+
|
16
|
+
t.timestamps null: false
|
17
|
+
end
|
18
|
+
|
19
|
+
add_index :subscription_client_notices, %i[notice_type notice_subject_type notice_subject_id changed_at],
|
20
|
+
unique: true, name: "sc_unique_notices"
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateSubscriptionClientSubscriptions < ActiveRecord::Migration[6.1]
|
4
|
+
def change
|
5
|
+
create_table :subscription_client_subscriptions do |t|
|
6
|
+
t.references :resource, foreign_key: { to_table: :subscription_client_resources }
|
7
|
+
t.string :product_id, null: false
|
8
|
+
t.string :product_name
|
9
|
+
t.string :price_id, null: false
|
10
|
+
t.string :price_name
|
11
|
+
t.boolean :subscribed, default: false, null: false
|
12
|
+
|
13
|
+
t.timestamps null: false
|
14
|
+
end
|
15
|
+
|
16
|
+
add_index :subscription_client_subscriptions, %i[resource_id product_id price_id], unique: true,
|
17
|
+
name: "sc_unique_subscriptions"
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreateSubscriptionClientRequests < ActiveRecord::Migration[7.0]
|
4
|
+
def change
|
5
|
+
create_table :subscription_client_requests do |t|
|
6
|
+
t.bigint :request_id
|
7
|
+
t.string :request_type
|
8
|
+
t.datetime :expired_at
|
9
|
+
t.string :message
|
10
|
+
t.integer :count
|
11
|
+
t.json :response
|
12
|
+
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DiscourseSubscriptionClient
|
4
|
+
class Authorization
|
5
|
+
SCOPE ||= "discourse-subscription-server:user_subscription"
|
6
|
+
|
7
|
+
def self.request_id(supplier_id)
|
8
|
+
"#{supplier_id}-#{SecureRandom.hex(32)}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.url(user, supplier, request_id)
|
12
|
+
keys = generate_keys(user.id, request_id)
|
13
|
+
params = {
|
14
|
+
public_key: keys.public_key,
|
15
|
+
nonce: keys.nonce,
|
16
|
+
client_id: client_id(user.id),
|
17
|
+
auth_redirect: "#{Discourse.base_url}/admin/plugins/subscription-client/suppliers/authorize/callback",
|
18
|
+
application_name: SiteSetting.title,
|
19
|
+
scopes: SCOPE
|
20
|
+
}
|
21
|
+
uri = URI.parse("#{supplier.url}/user-api-key/new")
|
22
|
+
uri.query = URI.encode_www_form(params)
|
23
|
+
uri.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.process_response(request_id, payload)
|
27
|
+
data = decrypt_payload(request_id, payload)
|
28
|
+
return false unless data.is_a?(Hash) && data[:key] && data[:user_id]
|
29
|
+
|
30
|
+
data
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.generate_keys(user_id, request_id)
|
34
|
+
rsa = OpenSSL::PKey::RSA.generate(2048)
|
35
|
+
nonce = SecureRandom.hex(32)
|
36
|
+
set_keys(request_id, user_id, rsa, nonce)
|
37
|
+
OpenStruct.new(nonce: nonce, public_key: rsa.public_key)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.decrypt_payload(request_id, payload)
|
41
|
+
keys = get_keys(request_id)
|
42
|
+
|
43
|
+
return false unless keys.present? && keys.pem
|
44
|
+
|
45
|
+
delete_keys(request_id)
|
46
|
+
|
47
|
+
rsa = OpenSSL::PKey::RSA.new(keys.pem)
|
48
|
+
decrypted_payload = rsa.private_decrypt(Base64.decode64(payload))
|
49
|
+
|
50
|
+
return false unless decrypted_payload.present?
|
51
|
+
|
52
|
+
begin
|
53
|
+
data = JSON.parse(decrypted_payload).symbolize_keys
|
54
|
+
rescue JSON::ParserError
|
55
|
+
return false
|
56
|
+
end
|
57
|
+
|
58
|
+
return false unless data[:nonce] == keys.nonce
|
59
|
+
|
60
|
+
data[:user_id] = keys.user_id
|
61
|
+
data
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.get_keys(request_id)
|
65
|
+
raw = PluginStore.get(DiscourseSubscriptionClient::PLUGIN_NAME, "#{keys_db_key}_#{request_id}")
|
66
|
+
OpenStruct.new(
|
67
|
+
user_id: raw && raw["user_id"],
|
68
|
+
pem: raw && raw["pem"],
|
69
|
+
nonce: raw && raw["nonce"]
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.revoke(supplier)
|
74
|
+
url = "#{supplier.url}/user-api-key/revoke"
|
75
|
+
request = DiscourseSubscriptionClient::Request.new(:supplier, supplier.id)
|
76
|
+
headers = { "User-Api-Key" => supplier.api_key }
|
77
|
+
result = request.perform(url, headers: headers, body: nil, opts: { method: "POST" })
|
78
|
+
result && result[:success] == "OK"
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.client_id(user_id)
|
82
|
+
"#{Discourse.current_hostname}:#{user_id}:#{SecureRandom.hex(8)}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.keys_db_key
|
86
|
+
"keys"
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.set_keys(request_id, user_id, rsa, nonce)
|
90
|
+
PluginStore.set(DiscourseSubscriptionClient::PLUGIN_NAME, "#{keys_db_key}_#{request_id}",
|
91
|
+
user_id: user_id,
|
92
|
+
pem: rsa.export,
|
93
|
+
nonce: nonce)
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.delete_keys(request_id)
|
97
|
+
PluginStore.remove(DiscourseSubscriptionClient::PLUGIN_NAME, "#{keys_db_key}_#{request_id}")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DiscourseSubscriptionClient
|
4
|
+
PLUGIN_NAME ||= "discourse_subscription_client"
|
5
|
+
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
engine_name PLUGIN_NAME
|
8
|
+
isolate_namespace DiscourseSubscriptionClient
|
9
|
+
|
10
|
+
config.before_initialize do
|
11
|
+
config.i18n.load_path += Dir["#{config.root}/config/locales/**/*.yml"]
|
12
|
+
end
|
13
|
+
|
14
|
+
config.after_initialize do
|
15
|
+
gem_root = File.expand_path("../..", __dir__)
|
16
|
+
|
17
|
+
ActiveRecord::Tasks::DatabaseTasks.migrations_paths << "#{gem_root}/db/migrate"
|
18
|
+
|
19
|
+
%w[
|
20
|
+
./request
|
21
|
+
./authorization
|
22
|
+
./resources
|
23
|
+
./notices
|
24
|
+
./subscriptions
|
25
|
+
./subscriptions/result
|
26
|
+
../../app/models/subscription_client_notice
|
27
|
+
../../app/models/subscription_client_resource
|
28
|
+
../../app/models/subscription_client_subscription
|
29
|
+
../../app/models/subscription_client_supplier
|
30
|
+
../../app/controllers/discourse_subscription_client/admin_controller
|
31
|
+
../../app/controllers/discourse_subscription_client/subscriptions_controller
|
32
|
+
../../app/controllers/discourse_subscription_client/suppliers_controller
|
33
|
+
../../app/controllers/discourse_subscription_client/notices_controller
|
34
|
+
../../app/serializers/discourse_subscription_client/supplier_serializer
|
35
|
+
../../app/serializers/discourse_subscription_client/resource_serializer
|
36
|
+
../../app/serializers/discourse_subscription_client/notice_serializer
|
37
|
+
../../app/serializers/discourse_subscription_client/subscription_serializer
|
38
|
+
../../app/jobs/regular/discourse_subscription_client/find_resources
|
39
|
+
../../app/jobs/scheduled/discourse_subscription_client/update_subscriptions
|
40
|
+
../../app/jobs/scheduled/discourse_subscription_client/update_notices
|
41
|
+
../../extensions/discourse_subscription_client/current_user
|
42
|
+
../../extensions/discourse_subscription_client/guardian
|
43
|
+
].each do |path|
|
44
|
+
require_relative path
|
45
|
+
end
|
46
|
+
|
47
|
+
Jobs.enqueue(:subscription_client_find_resources) if DiscourseSubscriptionClient.database_exists? && !Rails.env.test?
|
48
|
+
|
49
|
+
Rails.application.routes.append do
|
50
|
+
mount DiscourseSubscriptionClient::Engine, at: "/admin/plugins/subscription-client"
|
51
|
+
end
|
52
|
+
|
53
|
+
SiteSetting.load_settings("#{gem_root}/config/settings.yml", plugin: PLUGIN_NAME)
|
54
|
+
|
55
|
+
Guardian.prepend DiscourseSubscriptionClient::GuardianExtension
|
56
|
+
CurrentUserSerializer.prepend DiscourseSubscriptionClient::CurrentUserSerializerExtension
|
57
|
+
|
58
|
+
User.has_many(:subscription_client_suppliers)
|
59
|
+
|
60
|
+
AdminDashboardData.add_scheduled_problem_check(:subscription_client) do
|
61
|
+
return unless SiteSetting.subscription_client_warning_notices_on_dashboard
|
62
|
+
|
63
|
+
notices = SubscriptionClientNotice.list(
|
64
|
+
notice_type: SubscriptionClientNotice.error_types,
|
65
|
+
visible: true
|
66
|
+
)
|
67
|
+
notices.map do |notice|
|
68
|
+
AdminDashboardData::Problem.new(
|
69
|
+
"#{notice.title}: #{notice.message}",
|
70
|
+
priority: "high",
|
71
|
+
identifier: "subscription_client_notice_#{notice.id}"
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
DiscourseEvent.trigger(:subscription_client_ready)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class << self
|
81
|
+
def root
|
82
|
+
Rails.root
|
83
|
+
end
|
84
|
+
|
85
|
+
def plugin_status_server_url
|
86
|
+
"https://discourse.pluginmanager.org"
|
87
|
+
end
|
88
|
+
|
89
|
+
def database_exists?
|
90
|
+
ActiveRecord::Base.connection
|
91
|
+
rescue ActiveRecord::NoDatabaseError
|
92
|
+
false
|
93
|
+
else
|
94
|
+
true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DiscourseSubscriptionClient
|
4
|
+
class Notices
|
5
|
+
PLUGIN_STATUS_RESOURCE_ID = -1
|
6
|
+
PLUGIN_STATUSES_TO_WARN = %w[incompatible tests_failing].freeze
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@suppliers = SubscriptionClientSupplier.authorized
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.update(subscription: true, plugin: true)
|
13
|
+
new.update(subscription: subscription, plugin: plugin)
|
14
|
+
end
|
15
|
+
|
16
|
+
def update(subscription: true, plugin: true)
|
17
|
+
return if !SiteSetting.subscription_client_enabled || @suppliers.blank?
|
18
|
+
|
19
|
+
if subscription
|
20
|
+
@suppliers.each do |supplier|
|
21
|
+
update_subscription_messages(supplier)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
update_plugin_statuses if plugin && SiteSetting.subscription_client_request_plugin_statuses
|
26
|
+
|
27
|
+
SubscriptionClientNotice.publish_notice_count
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_subscription_messages(supplier)
|
31
|
+
url = "#{supplier.url}/subscription-server/messages"
|
32
|
+
request = DiscourseSubscriptionClient::Request.new(:supplier, supplier.id)
|
33
|
+
messages = request.perform(url)
|
34
|
+
|
35
|
+
return unless messages.present?
|
36
|
+
|
37
|
+
messages[:messages].each do |message|
|
38
|
+
notice_type = SubscriptionClientNotice.types[message[:type].to_sym]
|
39
|
+
|
40
|
+
if message[:resource] && (resource = SubscriptionClientResource.find_by(name: message[:resource],
|
41
|
+
supplier_id: supplier.id))
|
42
|
+
notice_subject_type = SubscriptionClientNotice.notice_subject_types[:resource]
|
43
|
+
notice_subject_id = resource.id
|
44
|
+
else
|
45
|
+
notice_subject_type = SubscriptionClientNotice.notice_subject_types[:supplier]
|
46
|
+
notice_subject_id = supplier.id
|
47
|
+
end
|
48
|
+
|
49
|
+
changed_at = message[:created_at]
|
50
|
+
notice = SubscriptionClientNotice.find_by(
|
51
|
+
notice_type: notice_type,
|
52
|
+
notice_subject_type: notice_subject_type,
|
53
|
+
notice_subject_id: notice_subject_id,
|
54
|
+
changed_at: changed_at
|
55
|
+
)
|
56
|
+
|
57
|
+
if notice
|
58
|
+
if message[:expired_at]
|
59
|
+
notice.expired_at = message[:expired_at]
|
60
|
+
notice.save
|
61
|
+
end
|
62
|
+
else
|
63
|
+
SubscriptionClientNotice.create!(
|
64
|
+
title: message[:title],
|
65
|
+
message: message[:message],
|
66
|
+
notice_type: notice_type,
|
67
|
+
notice_subject_type: notice_subject_type,
|
68
|
+
notice_subject_id: notice_subject_id,
|
69
|
+
changed_at: changed_at,
|
70
|
+
expired_at: message[:expired_at],
|
71
|
+
retrieved_at: DateTime.now.iso8601(3)
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def update_plugin_statuses
|
78
|
+
request = DiscourseSubscriptionClient::Request.new(:resource, PLUGIN_STATUS_RESOURCE_ID)
|
79
|
+
response = request.perform(DiscourseSubscriptionClient.plugin_status_server_url)
|
80
|
+
return false unless response && response[:statuses].present?
|
81
|
+
|
82
|
+
statuses = response[:statuses]
|
83
|
+
|
84
|
+
return unless statuses.present?
|
85
|
+
|
86
|
+
warnings = statuses.select { |status| PLUGIN_STATUSES_TO_WARN.include?(status[:status]) }
|
87
|
+
expiries = statuses - warnings
|
88
|
+
|
89
|
+
create_plugin_warning_notices(warnings) if warnings.any?
|
90
|
+
expire_plugin_warning_notices(expiries) if expiries.any?
|
91
|
+
end
|
92
|
+
|
93
|
+
def expire_plugin_warning_notices(expiries)
|
94
|
+
plugin_names = expiries.map { |expiry| expiry[:name] }
|
95
|
+
sql = <<~SQL
|
96
|
+
UPDATE subscription_client_notices AS notices
|
97
|
+
SET expired_at = now()
|
98
|
+
FROM subscription_client_resources AS resources
|
99
|
+
WHERE resources.name IN (:plugin_names)
|
100
|
+
AND notices.notice_subject_id = resources.id
|
101
|
+
AND notices.notice_subject_type = 'SubscriptionClientResource'
|
102
|
+
AND notices.notice_type = :notice_type
|
103
|
+
SQL
|
104
|
+
|
105
|
+
ActiveRecord::Base.connection.execute(
|
106
|
+
ActiveRecord::Base.sanitize_sql(
|
107
|
+
[
|
108
|
+
sql, {
|
109
|
+
notice_type: SubscriptionClientNotice.types[:warning],
|
110
|
+
plugin_names: plugin_names
|
111
|
+
}
|
112
|
+
]
|
113
|
+
)
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
def create_plugin_warning_notices(warnings)
|
118
|
+
plugin_names = warnings.map { |warning| warning[:name] }
|
119
|
+
resource_ids = SubscriptionClientResource.where(name: plugin_names)
|
120
|
+
.each_with_object({}) do |resource, result|
|
121
|
+
result[resource.name] =
|
122
|
+
resource.id
|
123
|
+
end
|
124
|
+
|
125
|
+
warnings.each do |warning|
|
126
|
+
notice_type = SubscriptionClientNotice.types[:warning]
|
127
|
+
notice_subject_type = SubscriptionClientNotice.notice_subject_types[:resource]
|
128
|
+
notice_subject_id = resource_ids[warning[:name]]
|
129
|
+
changed_at = warning[:status_changed_at]
|
130
|
+
|
131
|
+
notice = SubscriptionClientNotice.find_by(
|
132
|
+
notice_type: notice_type,
|
133
|
+
notice_subject_type: notice_subject_type,
|
134
|
+
notice_subject_id: notice_subject_id,
|
135
|
+
changed_at: changed_at
|
136
|
+
)
|
137
|
+
|
138
|
+
if notice
|
139
|
+
notice.touch
|
140
|
+
else
|
141
|
+
SubscriptionClientNotice.create!(
|
142
|
+
title: I18n.t("subscription_client.notices.compatibility_issue.title", resource: warning[:name]),
|
143
|
+
message: I18n.t("subscription_client.notices.compatibility_issue.message", resource: warning[:name]),
|
144
|
+
notice_type: notice_type,
|
145
|
+
notice_subject_type: notice_subject_type,
|
146
|
+
notice_subject_id: notice_subject_id,
|
147
|
+
changed_at: changed_at,
|
148
|
+
retrieved_at: DateTime.now.iso8601(3)
|
149
|
+
)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|