discourse_subscription_client 0.1.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +10 -0
  5. data/app/assets/config/discourse_subscription_client_manifest.js +1 -0
  6. data/app/assets/stylesheets/discourse_subscription_client/application.css +15 -0
  7. data/app/controllers/discourse_subscription_client/admin_controller.rb +46 -0
  8. data/app/controllers/discourse_subscription_client/notices_controller.rb +82 -0
  9. data/app/controllers/discourse_subscription_client/subscriptions_controller.rb +17 -0
  10. data/app/controllers/discourse_subscription_client/suppliers_controller.rb +62 -0
  11. data/app/jobs/regular/discourse_subscription_client/find_resources.rb +9 -0
  12. data/app/jobs/scheduled/discourse_subscription_client/update_notices.rb +11 -0
  13. data/app/jobs/scheduled/discourse_subscription_client/update_subscriptions.rb +11 -0
  14. data/app/models/subscription_client_notice.rb +223 -0
  15. data/app/models/subscription_client_request.rb +4 -0
  16. data/app/models/subscription_client_resource.rb +27 -0
  17. data/app/models/subscription_client_subscription.rb +54 -0
  18. data/app/models/subscription_client_supplier.rb +54 -0
  19. data/app/serializers/discourse_subscription_client/notice_serializer.rb +52 -0
  20. data/app/serializers/discourse_subscription_client/resource_serializer.rb +8 -0
  21. data/app/serializers/discourse_subscription_client/subscription_serializer.rb +12 -0
  22. data/app/serializers/discourse_subscription_client/supplier_serializer.rb +16 -0
  23. data/config/locales/server.en.yml +40 -0
  24. data/config/routes.rb +19 -0
  25. data/config/settings.yml +16 -0
  26. data/db/migrate/20220318160955_create_subscription_client_suppliers.rb +17 -0
  27. data/db/migrate/20220318181029_create_subscription_client_resources.rb +14 -0
  28. data/db/migrate/20220318181054_create_subscription_client_notices.rb +22 -0
  29. data/db/migrate/20220318181140_create_subscription_client_subscriptions.rb +19 -0
  30. data/db/migrate/20230220130259_create_subscription_client_requests.rb +16 -0
  31. data/lib/discourse_subscription_client/authorization.rb +100 -0
  32. data/lib/discourse_subscription_client/engine.rb +97 -0
  33. data/lib/discourse_subscription_client/notices.rb +154 -0
  34. data/lib/discourse_subscription_client/request.rb +136 -0
  35. data/lib/discourse_subscription_client/resources.rb +97 -0
  36. data/lib/discourse_subscription_client/subscriptions/result.rb +118 -0
  37. data/lib/discourse_subscription_client/subscriptions.rb +93 -0
  38. data/lib/discourse_subscription_client/version.rb +5 -0
  39. data/lib/discourse_subscription_client.rb +8 -0
  40. metadata +252 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b7303b45879b410ec868c784e77786d7c9c4b864e6cf02b840ac9b62ae7c2a29
4
+ data.tar.gz: f5c231e658b1023df67810547b9a982d0fe54a57e7a65582fd751067a6def9b7
5
+ SHA512:
6
+ metadata.gz: 31fe492137483fbb3ca7ead97f0ad583aa6b3f7a8a2fd57456776b57f767cd5c31ed897c85b2ed8ec35178996b75176a8f01eb584a304d693482f135e06a44ff
7
+ data.tar.gz: c6474026b3db22c4ab8de05f8e21d286a0140c4b638e504642a2c501bcd9efee0983539baf023c4f40d2902abbf5c11d90e1c608b8d4fcda8dbf9d3d53b1c352
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2023 Angus McLeod
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # DiscourseSubscriptionClient
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem "discourse_subscription_client"
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install discourse_subscription_client
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+
5
+ APP_RAKEFILE = File.expand_path("spec/support/discourse/Rakefile", __dir__)
6
+ load "rails/tasks/engine.rake"
7
+
8
+ load "rails/tasks/statistics.rake"
9
+
10
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/discourse_subscription_client .css
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DiscourseSubscriptionClient
4
+ class AdminController < ApplicationController
5
+ requires_login
6
+ before_action :ensure_can_manage_subscriptions
7
+
8
+ def index
9
+ respond_to do |format|
10
+ format.html do
11
+ render :index
12
+ end
13
+ format.json do
14
+ render_json_dump(
15
+ authorized_supplier_count: SubscriptionClientSupplier.authorized.count,
16
+ resource_count: SubscriptionClientResource.count
17
+ )
18
+ end
19
+ end
20
+ end
21
+
22
+ def ensure_can_manage_subscriptions
23
+ Guardian.new(current_user).ensure_can_manage_subscriptions!
24
+ end
25
+
26
+ def failed_json
27
+ { failed: "FAILED" }
28
+ end
29
+
30
+ def success_json
31
+ { success: "OK" }
32
+ end
33
+
34
+ def render_serialized(objects, serializer, opts = {})
35
+ render_json_dump(serialize_data(objects, serializer, opts))
36
+ end
37
+
38
+ def serialize_data(objects, serializer, opts = {})
39
+ ActiveModel::ArraySerializer.new(objects.to_a, opts.merge(each_serializer: serializer)).as_json
40
+ end
41
+
42
+ def render_json_dump(json)
43
+ render json: json, status: 200
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DiscourseSubscriptionClient
4
+ class NoticesController < AdminController
5
+ before_action :find_notice, only: %i[dismiss hide show]
6
+
7
+ def index
8
+ notice_type = params[:notice_type]
9
+ notice_subject_type = params[:notice_subject_type]
10
+ page = params[:page].to_i
11
+
12
+ visible = ActiveRecord::Type::Boolean.new.cast(params[:visible])
13
+
14
+ include_all = if current_user.admin
15
+ ActiveRecord::Type::Boolean.new.cast(params[:include_all])
16
+ else
17
+ false
18
+ end
19
+
20
+ if notice_type
21
+ notice_type = if notice_type.is_a?(Array)
22
+ notice_type.map { |t| SubscriptionClientNotice.types[t.to_sym] }
23
+ else
24
+ SubscriptionClientNotice.types[notice_type.to_sym]
25
+ end
26
+ end
27
+
28
+ if notice_subject_type
29
+ notice_subject_type = if notice_subject_type.is_a?(Array)
30
+ notice_subject_type.map { |t| SubscriptionClientNotice.notice_subject_types[t.to_sym] }
31
+ else
32
+ SubscriptionClientNotice.notice_subject_types[notice_subject_type.to_sym]
33
+ end
34
+ end
35
+
36
+ notices = SubscriptionClientNotice.list(
37
+ include_all: include_all,
38
+ page: page,
39
+ notice_type: notice_type,
40
+ notice_subject_type: notice_subject_type,
41
+ visible: visible
42
+ )
43
+
44
+ render_json_dump(
45
+ notices: serialize_data(notices, NoticeSerializer),
46
+ hidden_notice_count: SubscriptionClientNotice.hidden.count
47
+ )
48
+ end
49
+
50
+ def dismiss
51
+ if @notice.dismissable? && @notice.dismiss!
52
+ render json: success_json.merge(dismissed_at: @notice.dismissed_at)
53
+ else
54
+ render json: failed_json
55
+ end
56
+ end
57
+
58
+ def show
59
+ if @notice.hidden? && @notice.show!
60
+ render json: success_json
61
+ else
62
+ render json: failed_json
63
+ end
64
+ end
65
+
66
+ def hide
67
+ if @notice.can_hide? && @notice.hide!
68
+ render json: success_json.merge(hidden_at: @notice.hidden_at)
69
+ else
70
+ render json: failed_json
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def find_notice
77
+ params.require(:notice_id)
78
+ @notice = SubscriptionClientNotice.find(params[:notice_id])
79
+ raise Discourse::InvalidParameters, :notice_id unless @notice
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DiscourseSubscriptionClient
4
+ class SubscriptionsController < AdminController
5
+ def index
6
+ render_serialized(SubscriptionClientSubscription.all, SubscriptionSerializer, root: "subscriptions")
7
+ end
8
+
9
+ def update
10
+ if DiscourseSubscriptionClient::Subscriptions.update
11
+ render_serialized(SubscriptionClientSubscription.all, SubscriptionSerializer, root: "subscriptions")
12
+ else
13
+ render json: failed_json
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DiscourseSubscriptionClient
4
+ class SuppliersController < AdminController
5
+ before_action :ensure_can_manage_suppliers
6
+ skip_before_action :check_xhr, :preload_json, :verify_authenticity_token, only: %i[authorize authorize_callback]
7
+ before_action :find_supplier, only: %i[authorize destroy]
8
+
9
+ def index
10
+ render_serialized(SubscriptionClientSupplier.all, SupplierSerializer, root: "suppliers")
11
+ end
12
+
13
+ def authorize
14
+ request_id = DiscourseSubscriptionClient::Authorization.request_id(@supplier.id)
15
+ cookies[:user_api_request_id] = request_id
16
+ redirect_to DiscourseSubscriptionClient::Authorization.url(current_user, @supplier, request_id).to_s,
17
+ allow_other_host: true
18
+ end
19
+
20
+ def authorize_callback
21
+ payload = params[:payload]
22
+ request_id = cookies[:user_api_request_id]
23
+ supplier_id = request_id.split("-").first
24
+
25
+ data = DiscourseSubscriptionClient::Authorization.process_response(request_id, payload)
26
+ raise Discourse::InvalidParameters, :payload unless data
27
+
28
+ supplier = SubscriptionClientSupplier.find(supplier_id)
29
+ raise Discourse::InvalidParameters, :supplier_id unless supplier
30
+
31
+ supplier.update(
32
+ api_key: data[:key],
33
+ user_id: data[:user_id],
34
+ authorized_at: DateTime.now.iso8601(3)
35
+ )
36
+
37
+ DiscourseSubscriptionClient::Subscriptions.update
38
+
39
+ redirect_to "/admin/plugins/subscription-client/subscriptions"
40
+ end
41
+
42
+ def destroy
43
+ if @supplier.destroy_authorization
44
+ render json: success_json.merge(supplier: @supplier.reload)
45
+ else
46
+ render json: failed_json
47
+ end
48
+ end
49
+
50
+ protected
51
+
52
+ def find_supplier
53
+ params.require(:supplier_id)
54
+ @supplier = SubscriptionClientSupplier.find(params[:supplier_id])
55
+ raise Discourse::InvalidParameters, :supplier_id unless @supplier
56
+ end
57
+
58
+ def ensure_can_manage_suppliers
59
+ Guardian.new(current_user).ensure_can_manage_suppliers!
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jobs
4
+ class SubscriptionClientFindResources < ::Jobs::Base
5
+ def execute(_args = {})
6
+ ::DiscourseSubscriptionClient::Resources.find_all unless Rails.env.test?
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jobs
4
+ class SubscriptionClientUpdateNotices < ::Jobs::Scheduled
5
+ every 5.minutes
6
+
7
+ def execute(_args = {})
8
+ ::DiscourseSubscriptionClient::Notices.update
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jobs
4
+ class SubscriptionClientUpdateSubscriptions < ::Jobs::Scheduled
5
+ every 1.day
6
+
7
+ def execute(_args = {})
8
+ ::DiscourseSubscriptionClient::Subscriptions.update
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SubscriptionClientNotice < ActiveRecord::Base
4
+ belongs_to :notice_subject, polymorphic: true
5
+ delegate :name, to: :subject
6
+
7
+ scope :warnings, -> { where(notice_type: SubscriptionClientNotice.types[:warning]) }
8
+ scope :hidden, -> { where("hidden_at IS NOT NULL AND dismissed_at IS NULL AND expired_at IS NULL") }
9
+
10
+ def self.types
11
+ @types ||= {
12
+ info: 0,
13
+ warning: 1,
14
+ connection_error: 2
15
+ }
16
+ end
17
+
18
+ def self.notice_subject_types
19
+ @notice_subject_types ||= {
20
+ resource: "SubscriptionClientResource",
21
+ supplier: "SubscriptionClientSupplier"
22
+ }
23
+ end
24
+
25
+ def self.error_types
26
+ @error_types ||= [
27
+ types[:connection_error],
28
+ types[:warning]
29
+ ]
30
+ end
31
+
32
+ def supplier
33
+ if supplier?
34
+ notice_subject
35
+ elsif resource?
36
+ notice_subject.supplier
37
+ end
38
+ end
39
+
40
+ def resource
41
+ return unless resource?
42
+
43
+ notice_subject
44
+ end
45
+
46
+ def supplier?
47
+ notice_subject_type === self.class.notice_subject_types[:supplier]
48
+ end
49
+
50
+ def resource?
51
+ notice_subject_type === self.class.notice_subject_types[:resource] && !plugin_status_resource?
52
+ end
53
+
54
+ def plugin_status_resource?
55
+ DiscourseSubscriptionClient::Notices::PLUGIN_STATUS_RESOURCE_ID === notice_subject_id
56
+ end
57
+
58
+ def dismiss!
59
+ return unless dismissable?
60
+
61
+ self.dismissed_at = DateTime.now.iso8601(3)
62
+ save_and_publish
63
+ end
64
+
65
+ def hide!
66
+ if can_hide?
67
+ self.hidden_at = DateTime.now.iso8601(3)
68
+ save_and_publish
69
+ else
70
+ false
71
+ end
72
+ end
73
+
74
+ def show!
75
+ if hidden?
76
+ self.hidden_at = nil
77
+ save_and_publish
78
+ else
79
+ false
80
+ end
81
+ end
82
+
83
+ def expire!
84
+ if !expired?
85
+ self.expired_at = DateTime.now.iso8601(3)
86
+ save_and_publish
87
+ else
88
+ false
89
+ end
90
+ end
91
+
92
+ def save_and_publish
93
+ if save
94
+ self.class.publish_notice_count
95
+ else
96
+ false
97
+ end
98
+ end
99
+
100
+ def can_hide?
101
+ !hidden? && self.class.error_types.include?(notice_type) && (
102
+ notice_subject_type === self.class.notice_subject_types[:resource]
103
+ )
104
+ end
105
+
106
+ def expired?
107
+ expired_at.present?
108
+ end
109
+
110
+ def dismissed?
111
+ dismissed_at.present?
112
+ end
113
+
114
+ def dismissable?
115
+ !expired? && !dismissed? && notice_type === self.class.types[:info]
116
+ end
117
+
118
+ def hidden?
119
+ hidden_at.present?
120
+ end
121
+
122
+ def self.publish_notice_count
123
+ payload = {
124
+ visible_notice_count: list(visible: true).count
125
+ }
126
+ group_id_key = SiteSetting.subscription_client_allow_moderator_subscription_management ? :staff : :admins
127
+ MessageBus.publish("/subscription_client_user", payload, group_ids: [Group::AUTO_GROUPS[group_id_key.to_sym]])
128
+ end
129
+
130
+ def self.list(notice_type: nil, notice_subject_type: nil, notice_subject_id: nil, title: nil, include_all: false, visible: false, page: nil, page_limit: 30)
131
+ query = SubscriptionClientNotice.all
132
+ query = query.where("hidden_at IS NULL") if visible && !include_all
133
+ query = query.where("dismissed_at IS NULL") unless include_all
134
+ query = query.where("expired_at IS NULL") unless include_all
135
+ query = query.where("notice_subject_type = ?", notice_subject_type.to_s) if notice_subject_type
136
+ query = query.where("notice_subject_id = ?", notice_subject_id.to_i) if notice_subject_id
137
+ if notice_type
138
+ type_query_str = notice_type.is_a?(Array) ? "notice_type IN (?)" : "notice_type = ?"
139
+ query = query.where(type_query_str, notice_type)
140
+ end
141
+ query = query.where("title = ?", title) if title
142
+ query = query.limit(page_limit).offset(page.to_i * page_limit) unless page.nil?
143
+ query.order("expired_at DESC, updated_at DESC, dismissed_at DESC, created_at DESC")
144
+ end
145
+
146
+ def self.notify_connection_error(notice_subject_type_key, notice_subject_id)
147
+ notice_subject_type = notice_subject_types[notice_subject_type_key.to_sym]
148
+ return false unless notice_subject_type
149
+
150
+ notices = list(
151
+ notice_type: types[:connection_error],
152
+ notice_subject_type: notice_subject_type,
153
+ notice_subject_id: notice_subject_id
154
+ )
155
+ if notices.any?
156
+ notice = notices.first
157
+ notice.updated_at = DateTime.now.iso8601(3)
158
+ notice.save
159
+ else
160
+ opts = {}
161
+ if notice_subject_type === notice_subject_types[:supplier]
162
+ supplier = SubscriptionClientSupplier.find(notice_subject_id)
163
+ opts[:supplier] = supplier.name
164
+ end
165
+ create!(
166
+ title: I18n.t("subscription_client.notices.#{notice_subject_type_key}.connection_error.title", **opts),
167
+ message: I18n.t("subscription_client.notices.#{notice_subject_type_key}.connection_error.message", **opts),
168
+ notice_subject_type: notice_subject_type,
169
+ notice_subject_id: notice_subject_id,
170
+ notice_type: types[:connection_error],
171
+ created_at: DateTime.now.iso8601(3),
172
+ updated_at: DateTime.now.iso8601(3)
173
+ )
174
+ end
175
+ end
176
+
177
+ def self.expire_connection_error(notice_subject_type_key, notice_subject_id)
178
+ expired_count = expire_all(types[:connection_error], notice_subject_types[notice_subject_type_key.to_sym],
179
+ notice_subject_id)
180
+ publish_notice_count if expired_count.to_i.positive?
181
+ end
182
+
183
+ def self.dismiss_all
184
+ where("
185
+ notice_type = #{types[:info]} AND
186
+ expired_at IS NULL AND
187
+ dismissed_at IS NULL
188
+ ").update_all("dismissed_at = now()")
189
+ end
190
+
191
+ def self.expire_all(notice_type, notice_subject_type, notice_subject_id)
192
+ where("
193
+ notice_type = #{notice_type} AND
194
+ notice_subject_type = '#{notice_subject_type}' AND
195
+ notice_subject_id = #{notice_subject_id} AND
196
+ expired_at IS NULL
197
+ ").update_all("expired_at = now()")
198
+ end
199
+ end
200
+
201
+ # == Schema Information
202
+ #
203
+ # Table name: subscription_client_notices
204
+ #
205
+ # id :bigint not null, primary key
206
+ # title :string not null
207
+ # message :string
208
+ # notice_type :integer not null
209
+ # notice_subject_type :string
210
+ # notice_subject_id :bigint
211
+ # changed_at :datetime
212
+ # retrieved_at :datetime
213
+ # dismissed_at :datetime
214
+ # expired_at :datetime
215
+ # hidden_at :datetime
216
+ # created_at :datetime not null
217
+ # updated_at :datetime not null
218
+ #
219
+ # Indexes
220
+ #
221
+ # index_subscription_client_notices_on_notice_subject (notice_subject_type,notice_subject_id)
222
+ # sc_unique_notices (notice_type,notice_subject_type,notice_subject_id,changed_at) UNIQUE
223
+ #
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SubscriptionClientRequest < ActiveRecord::Base
4
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SubscriptionClientResource < ActiveRecord::Base
4
+ belongs_to :supplier, class_name: "SubscriptionClientSupplier"
5
+ has_many :notices, class_name: "SubscriptionClientNotice", as: :notice_subject, dependent: :destroy
6
+ has_many :subscriptions, foreign_key: "resource_id", class_name: "SubscriptionClientSubscription", dependent: :destroy
7
+ end
8
+
9
+ # == Schema Information
10
+ #
11
+ # Table name: subscription_client_resources
12
+ #
13
+ # id :bigint not null, primary key
14
+ # supplier_id :bigint
15
+ # name :string not null
16
+ # created_at :datetime not null
17
+ # updated_at :datetime not null
18
+ #
19
+ # Indexes
20
+ #
21
+ # index_subscription_client_resources_on_supplier_id (supplier_id)
22
+ # index_subscription_client_resources_on_supplier_id_and_name (supplier_id,name) UNIQUE
23
+ #
24
+ # Foreign Keys
25
+ #
26
+ # fk_rails_... (supplier_id => subscription_client_suppliers.id)
27
+ #
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SubscriptionClientSubscription < ActiveRecord::Base
4
+ validates :product_id, presence: true, uniqueness: { scope: :price_id }
5
+ validates :price_id, presence: true
6
+
7
+ belongs_to :resource, class_name: "SubscriptionClientResource"
8
+
9
+ scope :active, -> { where("subscribed = true AND updated_at > ?", SubscriptionClientSubscription.update_period) }
10
+
11
+ def active
12
+ subscribed && updated_at.to_datetime > self.class.update_period.to_datetime
13
+ end
14
+
15
+ def deactivate!
16
+ update(subscribed: false)
17
+ end
18
+
19
+ def resource_name
20
+ resource.name
21
+ end
22
+
23
+ def supplier_name
24
+ resource.supplier.name
25
+ end
26
+
27
+ def self.update_period
28
+ Time.zone.now - 2.days
29
+ end
30
+ end
31
+
32
+ # == Schema Information
33
+ #
34
+ # Table name: subscription_client_subscriptions
35
+ #
36
+ # id :bigint not null, primary key
37
+ # resource_id :bigint
38
+ # product_id :string not null
39
+ # product_name :string
40
+ # price_id :string not null
41
+ # price_name :string
42
+ # subscribed :boolean default(FALSE), not null
43
+ # created_at :datetime not null
44
+ # updated_at :datetime not null
45
+ #
46
+ # Indexes
47
+ #
48
+ # index_subscription_client_subscriptions_on_resource_id (resource_id)
49
+ # sc_unique_subscriptions (resource_id,product_id,price_id) UNIQUE
50
+ #
51
+ # Foreign Keys
52
+ #
53
+ # fk_rails_... (resource_id => subscription_client_resources.id)
54
+ #