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"}}
+
+
+
+
+{{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