discourse_subscription_client 0.1.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|