From a27c222dc66bc16e2efb996ef2d8b39ced3b8b3e Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Tue, 10 Aug 2021 14:45:23 +0800 Subject: [PATCH] Update authentication and subscription handling --- .../components/wizard-message.js.es6 | 1 + .../components/wizard-pro-subscription.js.es6 | 47 ++++++ .../controllers/admin-wizards-pro.js.es6 | 56 +++++++ .../custom-wizard-admin-route-map.js.es6 | 10 +- .../discourse/models/custom-wizard-pro.js.es6 | 34 ++++ .../discourse/models/custom-wizard.js.es6 | 2 +- .../discourse/routes/admin-wizards-pro.js.es6 | 20 +++ .../discourse/templates/admin-wizards-pro.hbs | 31 ++++ .../discourse/templates/admin-wizards.hbs | 1 + .../components/wizard-pro-subscription.hbs | 30 ++++ assets/stylesheets/common/wizard-admin.scss | 60 ++++++- config/locales/client.en.yml | 31 +++- config/routes.rb | 6 + controllers/custom_wizard/admin/pro.rb | 46 ++++++ .../{ => regular}/refresh_api_access_token.rb | 0 jobs/{ => regular}/set_after_time_wizard.rb | 0 jobs/scheduled/update_pro_status.rb | 11 ++ lib/custom_wizard/pro.rb | 21 +++ lib/custom_wizard/pro/authentication.rb | 155 ++++++++++++++++++ lib/custom_wizard/pro/subscription.rb | 74 +++++++++ plugin.rb | 12 +- .../pro/authentication_serializer.rb | 11 ++ .../pro/subscription_serializer.rb | 10 ++ serializers/custom_wizard/pro_serializer.rb | 26 +++ 24 files changed, 687 insertions(+), 8 deletions(-) create mode 100644 assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 create mode 100644 assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 create mode 100644 assets/javascripts/discourse/models/custom-wizard-pro.js.es6 create mode 100644 assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 create mode 100644 assets/javascripts/discourse/templates/admin-wizards-pro.hbs create mode 100644 assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs create mode 100644 controllers/custom_wizard/admin/pro.rb rename jobs/{ => regular}/refresh_api_access_token.rb (100%) rename jobs/{ => regular}/set_after_time_wizard.rb (100%) create mode 100644 jobs/scheduled/update_pro_status.rb create mode 100644 lib/custom_wizard/pro.rb create mode 100644 lib/custom_wizard/pro/authentication.rb create mode 100644 lib/custom_wizard/pro/subscription.rb create mode 100644 serializers/custom_wizard/pro/authentication_serializer.rb create mode 100644 serializers/custom_wizard/pro/subscription_serializer.rb create mode 100644 serializers/custom_wizard/pro_serializer.rb diff --git a/assets/javascripts/discourse/components/wizard-message.js.es6 b/assets/javascripts/discourse/components/wizard-message.js.es6 index b273e78b..686a7254 100644 --- a/assets/javascripts/discourse/components/wizard-message.js.es6 +++ b/assets/javascripts/discourse/components/wizard-message.js.es6 @@ -6,6 +6,7 @@ import I18n from "I18n"; const icons = { error: "times-circle", success: "check-circle", + warn: "exclamation-circle", info: "info-circle", }; diff --git a/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 b/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 new file mode 100644 index 00000000..8ea56699 --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-pro-subscription.js.es6 @@ -0,0 +1,47 @@ +import Component from "@ember/component"; +import CustomWizardPro from "../models/custom-wizard-pro"; +import { notEmpty } from "@ember/object/computed"; +import discourseComputed from "discourse-common/utils/decorators"; + +export default Component.extend({ + classNameBindings: [':custom-wizard-pro-subscription', 'subscription.active:active:inactive'], + subscribed: notEmpty('subscription'), + + @discourseComputed('subscription.type') + title(type) { + return type ? + I18n.t(`admin.wizard.pro.subscription.title.${type}`) : + I18n.t("admin.wizard.pro.not_subscribed"); + }, + + @discourseComputed('subscription.active') + stateClass(active) { + return active ? 'active' : 'inactive'; + }, + + @discourseComputed('stateClass') + stateLabel(stateClass) { + return I18n.t(`admin.wizard.pro.subscription.status.${stateClass}`); + }, + + actions: { + update() { + this.set('updating', true); + CustomWizardPro.update_subscription().then(result => { + if (result.success) { + this.setProperties({ + updateIcon: 'check', + subscription: result.subscription + }); + } else { + this.set('updateIcon', 'times'); + } + }).finally(() => { + this.set('updating', false); + setTimeout(() => { + this.set('updateIcon', null); + }, 7000); + }) + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 new file mode 100644 index 00000000..7c873c66 --- /dev/null +++ b/assets/javascripts/discourse/controllers/admin-wizards-pro.js.es6 @@ -0,0 +1,56 @@ +import Controller from "@ember/controller"; +import discourseComputed from "discourse-common/utils/decorators"; +import CustomWizardPro from "../models/custom-wizard-pro"; +import { alias } from "@ember/object/computed"; + +export default Controller.extend({ + messageUrl: "https://thepavilion.io/t/3652", + messageType: 'info', + messageKey: null, + showSubscription: alias('model.authentication.active'), + + setup() { + const authentication = this.get('model.authentication'); + const subscription = this.get('model.subscription'); + const subscribed = subscription && subscription.active; + const authenticated = authentication && authentication.active; + + if (!subscribed) { + this.set('messageKey', authenticated ? 'not_subscribed' : 'authorize'); + } else { + this.set('messageKey', !authenticated ? + 'subscription_expiring' : + subscribed ? 'subscription_active' : 'subscription_inactive' + ); + } + }, + + @discourseComputed('model.server') + messageOpts(server) { + return { server }; + }, + + actions: { + unauthorize() { + this.set('unauthorizing', true); + + CustomWizardPro.unauthorize().then(result => { + if (result.success) { + this.setProperties({ + messageKey: 'unauthorized', + messageType: 'warn', + "model.authentication": null, + "model.subscription": null + }); + } else { + this.setProperties({ + messageKey: 'unauthorize_failed', + messageType: 'error' + }); + } + }).finally(() => { + this.set('unauthorizing', false); + }) + } + } +}); diff --git a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 index 90ab5359..ec2f1b98 100644 --- a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 +++ b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 @@ -43,12 +43,20 @@ export default { } ); - this.route("adminWizardsLogs", { path: "/logs", resetNamespace: true }); + this.route("adminWizardsLogs", { + path: "/logs", + resetNamespace: true + }); this.route("adminWizardsManager", { path: "/manager", resetNamespace: true, }); + + this.route("adminWizardsPro", { + path: "/pro", + resetNamespace: true, + }); } ); }, diff --git a/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 b/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 new file mode 100644 index 00000000..66d80572 --- /dev/null +++ b/assets/javascripts/discourse/models/custom-wizard-pro.js.es6 @@ -0,0 +1,34 @@ +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; +import EmberObject from "@ember/object"; +import DiscourseURL from "discourse/lib/url"; + +const CustomWizardPro = EmberObject.extend(); + +const basePath = "/admin/wizards/pro"; + +CustomWizardPro.reopenClass({ + status() { + return ajax(basePath, { + type: "GET", + }).catch(popupAjaxError); + }, + + authorize() { + window.location.href = `${basePath}/authorize`; + }, + + unauthorize() { + return ajax(`${basePath}/authorize`, { + type: "DELETE", + }).catch(popupAjaxError); + }, + + update_subscription() { + return ajax(`${basePath}/subscription`, { + type: "POST", + }).catch(popupAjaxError); + } +}); + +export default CustomWizardPro; \ No newline at end of file diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6 index e6a8408d..80c4d86a 100644 --- a/assets/javascripts/discourse/models/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard.js.es6 @@ -221,7 +221,7 @@ CustomWizard.reopenClass({ const wizard = this._super.apply(this); wizard.setProperties(buildProperties(wizardJson)); return wizard; - }, + } }); export default CustomWizard; diff --git a/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 new file mode 100644 index 00000000..b6fdcb10 --- /dev/null +++ b/assets/javascripts/discourse/routes/admin-wizards-pro.js.es6 @@ -0,0 +1,20 @@ +import CustomWizardPro from "../models/custom-wizard-pro"; +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ + model() { + return CustomWizardPro.status(); + }, + + setupController(controller, model) { + console.log(model) + controller.set('model', model); + controller.setup(); + }, + + actions: { + authorize() { + CustomWizardPro.authorize(); + } + } +}); diff --git a/assets/javascripts/discourse/templates/admin-wizards-pro.hbs b/assets/javascripts/discourse/templates/admin-wizards-pro.hbs new file mode 100644 index 00000000..67a48a8f --- /dev/null +++ b/assets/javascripts/discourse/templates/admin-wizards-pro.hbs @@ -0,0 +1,31 @@ +
+

{{i18n "admin.wizard.pro.title"}}

+ +
+ {{#if model.authentication.active}} + {{conditional-loading-spinner size="small" condition=unauthorizing}} + + {{i18n "admin.wizard.pro.unauthorize"}} + + + {{else}} + {{d-button + icon="id-card" + label="admin.wizard.pro.authorize" + action=(route-action "authorize")}} + {{/if}} +
+
+ +{{wizard-message + key=messageKey + url=messageUrl + type=messageType + opts=messageOpts + component="pro"}} + +
+ {{#if showSubscription}} + {{wizard-pro-subscription subscription=model.subscription}} + {{/if}} +
diff --git a/assets/javascripts/discourse/templates/admin-wizards.hbs b/assets/javascripts/discourse/templates/admin-wizards.hbs index bd575aae..a2e104f7 100644 --- a/assets/javascripts/discourse/templates/admin-wizards.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards.hbs @@ -7,6 +7,7 @@ {{/if}} {{nav-item route="adminWizardsLogs" label="admin.wizard.log.nav_label"}} {{nav-item route="adminWizardsManager" label="admin.wizard.manager.nav_label"}} + {{nav-item route="adminWizardsPro" label="admin.wizard.pro.nav_label"}} {{/admin-nav}}
diff --git a/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs b/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs new file mode 100644 index 00000000..3d360220 --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-pro-subscription.hbs @@ -0,0 +1,30 @@ +
+

{{title}}

+ +
+ + {{#if updating}} + {{loading-spinner size="small"}} + {{else if updateIcon}} + {{d-icon updateIcon}} + {{/if}} + + {{d-button + icon="sync" + action=(action "update") + disabled=updating + label="admin.wizard.pro.subscription.update"}} +
+
+ +{{#if subscribed}} +
+
{{stateLabel}}
+
+ {{{i18n + 'admin.wizard.pro.subscription.last_updated' + updated_at=(format-date subscription.updated_at leaveAgo="true") + }}} +
+
+{{/if}} \ No newline at end of file diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss index 66cc6b43..b887dace 100644 --- a/assets/stylesheets/common/wizard-admin.scss +++ b/assets/stylesheets/common/wizard-admin.scss @@ -7,7 +7,7 @@ display: flex; align-items: center; justify-content: space-between; - margin-bottom: 20px; + margin-bottom: 10px; & + .wizard-message + div { margin-top: 20px; @@ -715,3 +715,61 @@ width: 80px; vertical-align: middle; } + +.admin-wizards-pro { + .admin-wizard-controls { + h3, label { + margin: 0; + } + + label { + padding: .4em .5em; + margin-left: .75em; + background-color: $success; + color: $secondary; + } + + .buttons { + display: flex; + align-items: center; + + .loading-container { + margin-right: 1em; + } + } + } + + .custom-wizard-pro-subscription { + .title-container { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: .5em; + + h3 { + margin: 0; + } + + .buttons > span { + margin-right: .5em; + } + } + + .detail-container { + display: flex; + align-items: center; + padding: 1em; + background-color: $primary-very-low; + + .subscription-state { + padding: .25em .5em; + margin-right: .75em; + + &.active { + background-color: $success; + color: $secondary; + } + } + } + } +} diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index ef826cab..e6a0e849 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -86,12 +86,20 @@ en: no_file: Please choose a file to import file_size_error: The file size must be 512kb or less file_format_error: The file must be a .json file - server_error: "Error: {{message}}" importing: Importing wizards... destroying: Destroying wizards... import_complete: Import complete destroy_complete: Destruction complete - + pro: + documentation: Check out the PRO documentation + authorize: "Authorize this forum to use your PRO subscription plan on %{server}." + not_subscribed: "You've authorized, but are not currently subscribed to a PRO plan on %{server}." + subscription_expiring: "Your subscription is active, but will expire in the next 48 hours." + subscription_active: "Your subscription is active." + subscription_inactive: "Your subscription is inactive on this forum. Read more in the documentation." + unauthorized: "You're unauthorized. If you have a subscription, it will become inactive in the next 48 hours." + unauthorize_failed: Failed to unauthorize. + editor: show: "Show" hide: "Hide" @@ -424,7 +432,24 @@ en: imported: imported upload: Select wizards.json destroy: Destroy - destroyed: destroyed + destroyed: destroyed + + pro: + nav_label: PRO + title: Custom Wizard PRO + authorize: Authorize + authorized: Authorized + unauthorize: cancel + not_subscribed: You're not currently subscribed + subscription: + title: + community: Community Subscription + business: Business Subscription + status: + active: Active + inactive: Inactive + update: Update + last_updated: Last updated {{updated_at}} wizard_js: group: diff --git a/config/routes.rb b/config/routes.rb index 28fcbb82..94cc5858 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -43,5 +43,11 @@ Discourse::Application.routes.append do get 'admin/wizards/manager/export' => 'admin_manager#export' post 'admin/wizards/manager/import' => 'admin_manager#import' delete 'admin/wizards/manager/destroy' => 'admin_manager#destroy' + + get 'admin/wizards/pro' => 'admin_pro#index' + get 'admin/wizards/pro/authorize' => 'admin_pro#authorize' + get 'admin/wizards/pro/authorize/callback' => 'admin_pro#authorize_callback' + delete 'admin/wizards/pro/authorize' => 'admin_pro#destroy' + post 'admin/wizards/pro/subscription' => 'admin_pro#update_subscription' end end diff --git a/controllers/custom_wizard/admin/pro.rb b/controllers/custom_wizard/admin/pro.rb new file mode 100644 index 00000000..b0686af2 --- /dev/null +++ b/controllers/custom_wizard/admin/pro.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class CustomWizard::AdminProController < CustomWizard::AdminController + skip_before_action :check_xhr, :preload_json, :verify_authenticity_token, only: [:authorize, :authorize_callback] + + def index + render_serialized(CustomWizard::Pro.new, CustomWizard::ProSerializer, root: false) + end + + def authorize + request_id = SecureRandom.hex(32) + cookies[:user_api_request_id] = request_id + redirect_to CustomWizard::ProAuthentication.generate_request(current_user.id, request_id).to_s + end + + def authorize_callback + payload = params[:payload] + request_id = cookies[:user_api_request_id] + + CustomWizard::ProAuthentication.handle_response(request_id, payload) + CustomWizard::ProSubscription.update + + redirect_to '/admin/wizards/pro' + end + + def destroy + if CustomWizard::ProAuthentication.destroy + render json: success_json + else + render json: failed_json + end + end + + def update_subscription + if CustomWizard::ProSubscription.update + render json: success_json.merge( + subscription: CustomWizard::ProSubscriptionSerializer.new( + CustomWizard::ProSubscription.new, + root: false + ) + ) + else + render json: failed_json + end + end +end \ No newline at end of file diff --git a/jobs/refresh_api_access_token.rb b/jobs/regular/refresh_api_access_token.rb similarity index 100% rename from jobs/refresh_api_access_token.rb rename to jobs/regular/refresh_api_access_token.rb diff --git a/jobs/set_after_time_wizard.rb b/jobs/regular/set_after_time_wizard.rb similarity index 100% rename from jobs/set_after_time_wizard.rb rename to jobs/regular/set_after_time_wizard.rb diff --git a/jobs/scheduled/update_pro_status.rb b/jobs/scheduled/update_pro_status.rb new file mode 100644 index 00000000..962f3ba9 --- /dev/null +++ b/jobs/scheduled/update_pro_status.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Jobs + class UpdateProSubscription < ::Jobs::Scheduled + every 1.days + + def execute(args) + CustomWizard::ProSubscription.update + end + end +end \ No newline at end of file diff --git a/lib/custom_wizard/pro.rb b/lib/custom_wizard/pro.rb new file mode 100644 index 00000000..c280c09d --- /dev/null +++ b/lib/custom_wizard/pro.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class CustomWizard::Pro + NAMESPACE ||= "#{CustomWizard::PLUGIN_NAME}_pro" + + attr_reader :authentication, + :subscription + + def initialize + @authentication = CustomWizard::ProAuthentication.new + @subscription = CustomWizard::ProSubscription.new + end + + def authorized? + @authentication.active? + end + + def subscribed? + @subscription.active? + end +end \ No newline at end of file diff --git a/lib/custom_wizard/pro/authentication.rb b/lib/custom_wizard/pro/authentication.rb new file mode 100644 index 00000000..c92710bc --- /dev/null +++ b/lib/custom_wizard/pro/authentication.rb @@ -0,0 +1,155 @@ +class CustomWizard::ProAuthentication + include ActiveModel::Serialization + + API_KEY ||= "api_key" + API_CLIENT_ID ||= 'api_client_id' + KEYS ||= "keys" + + attr_reader :client_id, + :auth_by, + :auth_at, + :api_key + + def initialize + api = get_api_key + + @api_key = api.key + @auth_at = api.auth_at + @auth_by = api.auth_by + @client_id = get_client_id || set_client_id + end + + def active? + @api_key.present? + end + + def update(data) + api_key = data[:key] + user_id = data[:user_id] + user = User.find(user_id) + + if user&.admin + set_api_key(api_key, user.id) + else + false + end + end + + def destroy + remove + end + + def self.destroy + self.new.destroy + end + + def generate_keys(user_id, request_id) + rsa = OpenSSL::PKey::RSA.generate(2048) + nonce = SecureRandom.hex(32) + set_keys(request_id, user_id, rsa, nonce) + + OpenStruct.new(nonce: nonce, public_key: rsa.public_key) + end + + def decrypt_payload(request_id, payload) + keys = get_keys(request_id) + return false unless keys.present? && keys.pem + delete_keys(request_id) + + rsa = OpenSSL::PKey::RSA.new(keys.pem) + decrypted_payload = rsa.private_decrypt(Base64.decode64(payload)) + return false unless decrypted_payload.present? + + begin + data = JSON.parse(decrypted_payload).symbolize_keys + rescue JSON::ParserError + return false + end + + return false unless data[:nonce] == keys.nonce + data[:user_id] = keys.user_id + + data + end + + def self.generate_request(user_id, request_id) + authentication = self.new + keys = authentication.generate_keys(user_id, request_id) + + params = { + public_key: keys.public_key, + nonce: keys.nonce, + client_id: authentication.client_id, + auth_redirect: "#{Discourse.base_url}/admin/wizards/pro/authorize/callback", + application_name: SiteSetting.title, + scopes: CustomWizard::ProSubscription::SCOPE + } + + uri = URI.parse("https://#{CustomWizard::ProSubscription::SUBSCRIPTION_SERVER}/user-api-key/new") + uri.query = URI.encode_www_form(params) + uri.to_s + end + + def self.handle_response(request_id, payload) + authentication = self.new + + data = authentication.decrypt_payload(request_id, payload) + return unless data.is_a?(Hash) && data[:key] && data[:user_id] + + authentication.update(data) + end + + private + + def get_api_key + raw = PluginStore.get(CustomWizard::Pro::NAMESPACE, API_KEY) + OpenStruct.new( + key: raw && raw['key'], + auth_by: raw && raw['auth_by'], + auth_at: raw && raw['auth_at'] + ) + end + + def set_api_key(key, user_id) + PluginStore.set(CustomWizard::Pro::NAMESPACE, API_KEY, + key: key, + auth_by: user_id, + auth_at: Time.now + ) + end + + def remove + PluginStore.remove(CustomWizard::Pro::NAMESPACE, API_KEY) + end + + def get_client_id + PluginStore.get(CustomWizard::Pro::NAMESPACE, API_CLIENT_ID) + end + + def set_client_id + client_id = SecureRandom.hex(32) + PluginStore.set(CustomWizard::Pro::NAMESPACE, API_CLIENT_ID, client_id) + client_id + end + + def set_keys(request_id, user_id, rsa, nonce) + PluginStore.set(CustomWizard::Pro::NAMESPACE, "#{KEYS}_#{request_id}", + user_id: user_id, + pem: rsa.export, + nonce: nonce + ) + end + + def get_keys(request_id) + raw = PluginStore.get(CustomWizard::Pro::NAMESPACE, "#{KEYS}_#{request_id}") + OpenStruct.new( + user_id: raw && raw['user_id'], + pem: raw && raw['pem'], + nonce: raw && raw['nonce'] + ) + end + + def delete_keys(request_id) + PluginStore.remove(CustomWizard::Pro::NAMESPACE, "#{KEYS}_#{request_id}") + end +end \ No newline at end of file diff --git a/lib/custom_wizard/pro/subscription.rb b/lib/custom_wizard/pro/subscription.rb new file mode 100644 index 00000000..2e0c8542 --- /dev/null +++ b/lib/custom_wizard/pro/subscription.rb @@ -0,0 +1,74 @@ +class CustomWizard::ProSubscription + include ActiveModel::Serialization + + SUBSCRIPTION_SERVER ||= "test.thepavilion.io" + SUBSCRIPTION_TYPE ||= "stripe" + SCOPE ||= "discourse-subscription-server:user_subscription" + CLIENT_NAME ||= "custom-wizard" + SUBSCRIPTION_KEY ||= "custom_wizard_pro_subscription" + UPDATE_DAY_BUFFER ||= 2 + TYPES ||= %w(community business) + + attr_reader :type, + :updated_at + + def initialize + raw = get + + if raw + @type = raw['type'] + @updated_at = raw['updated_at'] + end + end + + def active? + TYPES.include?(type) && updated_at.to_datetime > (Date.today - UPDATE_DAY_BUFFER.days).to_datetime + end + + def update(data) + return false unless data && data.is_a?(Hash) + subscriptions = data[:subscriptions] + + if subscriptions.present? + subscription = subscriptions.first + type = subscription[:price_nickname] + + set(type) + end + end + + def self.update + @subscribed = nil + auth = CustomWizard::ProAuthentication.new + subscription = self.new + + if auth.active? + response = Excon.get( + "https://#{SUBSCRIPTION_SERVER}/subscription-server/user-subscriptions/#{SUBSCRIPTION_TYPE}/#{CLIENT_NAME}", + headers: { "User-Api-Key" => auth.api_key } + ) + + if response.status == 200 + begin + data = JSON.parse(response.body).deep_symbolize_keys + rescue JSON::ParserError + return false + end + + return subscription.update(data) + end + end + + false + end + + private + + def set(type) + PluginStore.set(CustomWizard::Pro::NAMESPACE, SUBSCRIPTION_KEY, type: type, updated_at: Time.now) + end + + def get + PluginStore.get(CustomWizard::Pro::NAMESPACE, SUBSCRIPTION_KEY) + end +end \ No newline at end of file diff --git a/plugin.rb b/plugin.rb index 449d0237..97ed8e5a 100644 --- a/plugin.rb +++ b/plugin.rb @@ -62,11 +62,13 @@ after_initialize do ../controllers/custom_wizard/admin/logs.rb ../controllers/custom_wizard/admin/manager.rb ../controllers/custom_wizard/admin/custom_fields.rb + ../controllers/custom_wizard/admin/pro.rb ../controllers/custom_wizard/wizard.rb ../controllers/custom_wizard/steps.rb ../controllers/custom_wizard/realtime_validations.rb - ../jobs/refresh_api_access_token.rb - ../jobs/set_after_time_wizard.rb + ../jobs/regular/refresh_api_access_token.rb + ../jobs/regular/set_after_time_wizard.rb + ../jobs/scheduled/update_pro_status.rb ../lib/custom_wizard/validators/template.rb ../lib/custom_wizard/validators/update.rb ../lib/custom_wizard/action_result.rb @@ -85,6 +87,9 @@ after_initialize do ../lib/custom_wizard/submission.rb ../lib/custom_wizard/template.rb ../lib/custom_wizard/wizard.rb + ../lib/custom_wizard/pro.rb + ../lib/custom_wizard/pro/subscription.rb + ../lib/custom_wizard/pro/authentication.rb ../lib/custom_wizard/api/api.rb ../lib/custom_wizard/api/authorization.rb ../lib/custom_wizard/api/endpoint.rb @@ -105,6 +110,9 @@ after_initialize do ../serializers/custom_wizard/log_serializer.rb ../serializers/custom_wizard/submission_serializer.rb ../serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb + ../serializers/custom_wizard/pro_serializer.rb + ../serializers/custom_wizard/pro/authentication_serializer.rb + ../serializers/custom_wizard/pro/subscription_serializer.rb ../extensions/extra_locales_controller.rb ../extensions/invites_controller.rb ../extensions/users_controller.rb diff --git a/serializers/custom_wizard/pro/authentication_serializer.rb b/serializers/custom_wizard/pro/authentication_serializer.rb new file mode 100644 index 00000000..b54f428f --- /dev/null +++ b/serializers/custom_wizard/pro/authentication_serializer.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +class CustomWizard::ProAuthenticationSerializer < ApplicationSerializer + attributes :active, + :client_id, + :auth_by, + :auth_at + + def active + object.active? + end +end \ No newline at end of file diff --git a/serializers/custom_wizard/pro/subscription_serializer.rb b/serializers/custom_wizard/pro/subscription_serializer.rb new file mode 100644 index 00000000..6be5ec6f --- /dev/null +++ b/serializers/custom_wizard/pro/subscription_serializer.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +class CustomWizard::ProSubscriptionSerializer < ApplicationSerializer + attributes :type, + :active, + :updated_at + + def active + object.active? + end +end \ No newline at end of file diff --git a/serializers/custom_wizard/pro_serializer.rb b/serializers/custom_wizard/pro_serializer.rb new file mode 100644 index 00000000..5b351f29 --- /dev/null +++ b/serializers/custom_wizard/pro_serializer.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true +class CustomWizard::ProSerializer < ApplicationSerializer + attributes :server, + :authentication, + :subscription + + def server + CustomWizard::ProSubscription::SUBSCRIPTION_SERVER + end + + def authentication + if object.authentication + CustomWizard::ProAuthenticationSerializer.new(object.authentication, root: false) + else + nil + end + end + + def subscription + if object.subscription + CustomWizard::ProSubscriptionSerializer.new(object.subscription, root: false) + else + nil + end + end +end \ No newline at end of file