0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-11-22 09:20:29 +01:00

Improve PRO feature approach

Dieser Commit ist enthalten in:
angusmcleod 2021-09-01 10:19:00 +08:00
Ursprung a810155f91
Commit 6b1e7568c1
22 geänderte Dateien mit 331 neuen und 314 gelöschten Zeilen

Datei anzeigen

@ -6,7 +6,8 @@ import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n"; import I18n from "I18n";
export default Component.extend({ export default Component.extend({
classNames: ["realtime-validations"], classNames: ["realtime-validations", "setting", "full", "pro"],
@discourseComputed @discourseComputed
timeUnits() { timeUnits() {
return ["days", "weeks", "months", "years"].map((unit) => { return ["days", "weeks", "months", "years"].map((unit) => {

Datei anzeigen

@ -77,7 +77,12 @@ export default Controller.extend({
wizard wizard
.save(opts) .save(opts)
.then((result) => { .then((result) => {
this.send("afterSave", result.wizard_id); console.log(result)
if (result.wizard_id) {
this.send("afterSave", result.wizard_id);
} else if (result.errors) {
this.set('error', result.errors.join(', '));
}
}) })
.catch((result) => { .catch((result) => {
let errorType = "failed"; let errorType = "failed";

Datei anzeigen

@ -220,60 +220,54 @@
options=fieldConditionOptions}} options=fieldConditionOptions}}
</div> </div>
</div> </div>
{{/if}}
<div class="setting full field-mapper-setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.index"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
{{#if showAdvanced}} <div class="setting-value">
{{wizard-advanced-toggle showAdvanced=field.showAdvanced}} {{wizard-mapper
inputs=field.index
options=fieldIndexOptions}}
</div>
</div>
{{#if field.showAdvanced}} {{#if isCategory}}
<div class="advanced-settings"> <div class="setting pro">
<div class="setting-label">
<div class="setting full field-mapper-setting"> <label>{{i18n "admin.wizard.field.property"}}</label>
<div class="setting-label"> <span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
<label>{{i18n "admin.wizard.index"}}</label>
</div>
<div class="setting-value">
{{wizard-mapper
inputs=field.index
options=fieldIndexOptions}}
</div>
</div> </div>
{{#if isCategory}} <div class="setting-value">
<div class="setting"> {{combo-box
<div class="setting-label"> value=field.property
<label>{{i18n "admin.wizard.field.property"}}</label> content=categoryPropertyTypes
</div> onChange=(action (mut field.property))
options=(hash
<div class="setting-value"> none="admin.wizard.selector.placeholder.property"
{{combo-box )}}
value=field.property
content=categoryPropertyTypes
onChange=(action (mut field.property))
options=(hash
none="admin.wizard.selector.placeholder.property"
)}}
</div>
</div>
{{/if}}
<div class="setting">
<div class="setting-label">
<label>{{i18n "admin.wizard.translation"}}</label>
</div>
<div class="setting-value medium">
{{input
name="key"
value=field.key
class="medium"
placeholderKey="admin.wizard.translation_placeholder"}}
</div>
</div> </div>
{{#if validations}}
{{wizard-realtime-validations field=field validations=validations}}
{{/if}}
</div> </div>
{{/if}} {{/if}}
<div class="setting pro">
<div class="setting-label">
<label>{{i18n "admin.wizard.translation"}}</label>
<span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
</div>
<div class="setting-value medium">
{{input
name="key"
value=field.key
class="medium"
placeholderKey="admin.wizard.translation_placeholder"}}
</div>
</div>
{{#if validations}}
{{wizard-realtime-validations field=field validations=validations}}
{{/if}}
{{/if}} {{/if}}

Datei anzeigen

@ -21,8 +21,11 @@
{{#if subscribed}} {{#if subscribed}}
<div class="detail-container"> <div class="detail-container">
<div class="subscription-state {{stateClass}}" title={{stateLabel}}>{{stateLabel}}</div> <div class="subscription-state {{stateClass}}" title={{stateLabel}}>{{stateLabel}}</div>
<div class="subscription-updated-at" title={{subscription.updated_at}}>
{{{i18n 'admin.wizard.pro.subscription.last_updated' updated_at=(format-date subscription.updated_at leaveAgo="true")}}} {{#if subscription.updated_at}}
</div> <div class="subscription-updated-at" title={{subscription.updated_at}}>
{{{i18n 'admin.wizard.pro.subscription.last_updated' updated_at=(format-date subscription.updated_at leaveAgo="true")}}}
</div>
{{/if}}
</div> </div>
{{/if}} {{/if}}

Datei anzeigen

@ -1,50 +1,54 @@
<h3>{{i18n "admin.wizard.field.validations.header"}}</h3> <div class="setting-label">
<label>{{i18n "admin.wizard.field.validations.header"}}</label>
<ul> <span class="pro-label">{{i18n "admin.wizard.pro.label"}}</span>
{{#each-in field.validations as |type props|}} </div>
<li> <div class="setting-value full">
<span class="setting-title"> <ul>
<h4>{{i18n (concat "admin.wizard.field.validations." type)}}</h4> {{#each-in field.validations as |type props|}}
{{input type="checkbox" checked=props.status}} <li>
{{i18n "admin.wizard.field.validations.enabled"}} <span class="setting-title">
</span> <h4>{{i18n (concat "admin.wizard.field.validations." type)}}</h4>
<div class="validation-container"> {{input type="checkbox" checked=props.status}}
<div class="validation-section"> {{i18n "admin.wizard.field.validations.enabled"}}
<div class="setting-label"> </span>
<label>{{i18n "admin.wizard.field.validations.categories"}}</label> <div class="validation-container">
<div class="validation-section">
<div class="setting-label">
<label>{{i18n "admin.wizard.field.validations.categories"}}</label>
</div>
<div class="setting-value">
{{category-selector
categories=(get this (concat "validationBuffer." type ".categories"))
onChange=(action "updateValidationCategories" type props)
class="wizard"}}
</div>
</div> </div>
<div class="setting-value"> <div class="validation-section">
{{category-selector <div class="setting-label">
categories=(get this (concat "validationBuffer." type ".categories")) <label>{{i18n "admin.wizard.field.validations.max_topic_age"}}</label>
onChange=(action "updateValidationCategories" type props) </div>
class="wizard"}} <div class="setting-value">
{{input type="number" class="time-n-value" value=props.time_n_value}}
{{combo-box
value=(readonly props.time_unit)
content=timeUnits
class="time-unit-selector"
onChange=(action (mut props.time_unit))}}
</div>
</div>
<div class="validation-section">
<div class="setting-label">
<label>{{i18n "admin.wizard.field.validations.position"}}</label>
</div>
<div class="setting-value">
{{radio-button name=(concat type field.id) value="above" selection=props.position}}
<span>{{i18n "admin.wizard.field.validations.above"}}</span>
{{radio-button name=(concat type field.id) value="below" selection=props.position}}
<span>{{i18n "admin.wizard.field.validations.below"}}</span>
</div>
</div> </div>
</div> </div>
<div class="validation-section"> </li>
<div class="setting-label"> {{/each-in}}
<label>{{i18n "admin.wizard.field.validations.max_topic_age"}}</label> </ul>
</div> </div>
<div class="setting-value">
{{input type="number" class="time-n-value" value=props.time_n_value}}
{{combo-box
value=(readonly props.time_unit)
content=timeUnits
class="time-unit-selector"
onChange=(action (mut props.time_unit))}}
</div>
</div>
<div class="validation-section">
<div class="setting-label">
<label>{{i18n "admin.wizard.field.validations.position"}}</label>
</div>
<div class="setting-value">
{{radio-button name=(concat type field.id) value="above" selection=props.position}}
{{i18n "admin.wizard.field.validations.above"}}
{{radio-button name=(concat type field.id) value="below" selection=props.position}}
{{i18n "admin.wizard.field.validations.below"}}
</div>
</div>
</div>
</li>
{{/each-in}}
</ul>

Datei anzeigen

@ -694,27 +694,59 @@
} }
} }
.realtime-validations > ul { .admin-wizard-container.settings .realtime-validations .setting-value > ul {
list-style: none; list-style: none;
margin: 0; margin: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
> li { > li {
background-color: var(--primary-low); background-color: var(--primary-low);
padding: 1em; padding: 1em;
margin: 0 0 1em 0; margin: 0 0 1em 0;
.setting-title {
display: flex;
align-items: center;
input { h4 {
margin-bottom: 0; margin: 0 15px 0 0;
}
input[type="checkbox"] {
margin: 0 5px 0 0;
}
}
.setting-label {
width: 100px;
}
.setting-value {
display: flex;
align-items: center;
.input .select-kit,
> .select-kit {
max-width: unset !important;
}
> span {
margin-right: 1em;
}
} }
} }
} }
.validation-container { .validation-container {
display: flex; display: flex;
flex-direction: column;
padding: 1em 0; padding: 1em 0;
.validation-section { .validation-section {
width: 250px; min-width: 250px;
margin: .5em 0;
} }
} }

Datei anzeigen

@ -192,7 +192,7 @@ en:
label: "Format" label: "Format"
instructions: "<a href='https://momentjs.com/docs/#/displaying/format/' target='_blank'>Moment.js format</a>" instructions: "<a href='https://momentjs.com/docs/#/displaying/format/' target='_blank'>Moment.js format</a>"
validations: validations:
header: "Realtime Validations" header: "Validations"
enabled: "Enabled" enabled: "Enabled"
similar_topics: "Similar Topics" similar_topics: "Similar Topics"
position: "Position" position: "Position"

Datei anzeigen

@ -17,7 +17,7 @@ en:
name_too_short: "'%{name}' is too short for a custom field name (min length is #{min_length})" name_too_short: "'%{name}' is too short for a custom field name (min length is #{min_length})"
name_already_taken: "'%{name}' is already taken as a custom field name" name_already_taken: "'%{name}' is already taken as a custom field name"
save_default: "Failed to save custom field '%{name}'" save_default: "Failed to save custom field '%{name}'"
pro_required: "PRO Actions require a PRO Subscription" pro_type: "%{type} custom fields require PRO Subscription"
field: field:
too_short: "%{label} must be at least %{min} characters" too_short: "%{label} must be at least %{min} characters"
@ -50,7 +50,7 @@ en:
required: "%{property} is required" required: "%{property} is required"
conflict: "Wizard with id '%{wizard_id}' already exists" conflict: "Wizard with id '%{wizard_id}' already exists"
after_time: "After time setting is invalid" after_time: "After time setting is invalid"
pro: "%{property} is PRO only" pro: "%{type} %{property} is PRO only"
site_settings: site_settings:
custom_wizard_enabled: "Enable custom wizards." custom_wizard_enabled: "Enable custom wizards."

Datei anzeigen

@ -47,7 +47,7 @@ Discourse::Application.routes.append do
get 'admin/wizards/pro' => 'admin_pro#index' get 'admin/wizards/pro' => 'admin_pro#index'
get 'admin/wizards/pro/authorize' => 'admin_pro#authorize' get 'admin/wizards/pro/authorize' => 'admin_pro#authorize'
get 'admin/wizards/pro/authorize/callback' => 'admin_pro#authorize_callback' get 'admin/wizards/pro/authorize/callback' => 'admin_pro#authorize_callback'
delete 'admin/wizards/pro/authorize' => 'admin_pro#destroy' delete 'admin/wizards/pro/authorize' => 'admin_pro#destroy_authentication'
post 'admin/wizards/pro/subscription' => 'admin_pro#update_subscription' post 'admin/wizards/pro/subscription' => 'admin_pro#update_subscription'
end end
end end

Datei anzeigen

@ -4,27 +4,27 @@ class CustomWizard::AdminProController < CustomWizard::AdminController
skip_before_action :check_xhr, :preload_json, :verify_authenticity_token, only: [:authorize, :authorize_callback] skip_before_action :check_xhr, :preload_json, :verify_authenticity_token, only: [:authorize, :authorize_callback]
def index def index
render_serialized(CustomWizard::Pro.new, CustomWizard::ProSerializer, root: false) render_serialized(pro, CustomWizard::ProSerializer, root: false)
end end
def authorize def authorize
request_id = SecureRandom.hex(32) request_id = SecureRandom.hex(32)
cookies[:user_api_request_id] = request_id cookies[:user_api_request_id] = request_id
redirect_to CustomWizard::Pro.auth_request(current_user.id, request_id).to_s redirect_to pro.authentication_request(current_user.id, request_id).to_s
end end
def authorize_callback def authorize_callback
payload = params[:payload] payload = params[:payload]
request_id = cookies[:user_api_request_id] request_id = cookies[:user_api_request_id]
CustomWizard::Pro.auth_response(request_id, payload) pro.authentication_response(request_id, payload)
CustomWizard::Pro.update_subscription pro.update_subscription
redirect_to '/admin/wizards/pro' redirect_to '/admin/wizards/pro'
end end
def destroy def destroy_authentication
if CustomWizard::ProAuthentication.destroy if pro.destroy_authentication
render json: success_json render json: success_json
else else
render json: failed_json render json: failed_json
@ -32,15 +32,17 @@ class CustomWizard::AdminProController < CustomWizard::AdminController
end end
def update_subscription def update_subscription
if CustomWizard::Pro.update if pro.update_subscription
render json: success_json.merge( subscription = CustomWizard::ProSubscriptionSerializer.new(pro.subscription, root: false)
subscription: CustomWizard::ProSubscriptionSerializer.new( render json: success_json.merge(subscription: subscription)
CustomWizard::ProSubscription.new,
root: false
)
)
else else
render json: failed_json render json: failed_json
end end
end end
protected
def pro
@pro ||= CustomWizard::Pro.new
end
end end

Datei anzeigen

@ -5,6 +5,7 @@ class CustomWizard::WizardController < ::ApplicationController
layout 'wizard' layout 'wizard'
before_action :ensure_plugin_enabled before_action :ensure_plugin_enabled
before_action :update_pro_subscription
helper_method :wizard_page_title helper_method :wizard_page_title
helper_method :wizard_theme_id helper_method :wizard_theme_id
helper_method :wizard_theme_lookup helper_method :wizard_theme_lookup
@ -81,4 +82,8 @@ class CustomWizard::WizardController < ::ApplicationController
redirect_to path("/") redirect_to path("/")
end end
end end
def update_pro_subscription
CustomWizard::Pro.update_subscription
end
end end

Datei anzeigen

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class CustomWizard::UpdateProSubscription < ::Jobs::Scheduled class CustomWizard::UpdateProSubscription < ::Jobs::Scheduled
every 10.minutes every 1.hour
def execute(args) def execute(args)
CustomWizard::Pro.update_subscription CustomWizard::Pro.update_subscription

Datei anzeigen

@ -14,15 +14,9 @@ class CustomWizard::Action
@submission = opts[:submission] @submission = opts[:submission]
@log = [] @log = []
@result = CustomWizard::ActionResult.new @result = CustomWizard::ActionResult.new
@pro = CustomWizard::Pro.new
end end
def perform def perform
if pro_actions.include?(action['type']) && !@pro.subscribed?
log_error(I18n.t("wizard.custom_field.error.pro_required"))
return
end
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
self.send(action['type'].to_sym) self.send(action['type'].to_sym)
end end

Datei anzeigen

@ -6,7 +6,6 @@ class CustomWizard::Builder
@template = CustomWizard::Template.create(wizard_id) @template = CustomWizard::Template.create(wizard_id)
return nil if @template.nil? return nil if @template.nil?
@wizard = CustomWizard::Wizard.new(template.data, user) @wizard = CustomWizard::Wizard.new(template.data, user)
@pro = CustomWizard::Pro.new
end end
def self.sorted_handlers def self.sorted_handlers
@ -226,11 +225,6 @@ class CustomWizard::Builder
end end
def check_condition(template) def check_condition(template)
unless @pro.subscribed?
CustomWizard::Log.create(I18n.t("wizard.custom_field.error.pro_required"))
return false
end
if template['condition'].present? if template['condition'].present?
result = CustomWizard::Mapper.new( result = CustomWizard::Mapper.new(
inputs: template['condition'], inputs: template['condition'],

Datei anzeigen

@ -37,6 +37,8 @@ class ::CustomWizard::CustomField
send("#{attr}=", value) send("#{attr}=", value)
end end
end end
@pro = CustomWizard::Pro.new
end end
def save def save
@ -92,6 +94,10 @@ class ::CustomWizard::CustomField
add_error(I18n.t("#{i18n_key}.unsupported_type", type: value)) add_error(I18n.t("#{i18n_key}.unsupported_type", type: value))
end end
if attr == 'type' && value == 'json' && !@pro.subscribed?
add_error(I18n.t("wizard.custom_field.error.pro_type", type: value))
end
if attr == 'name' if attr == 'name'
unless value.is_a?(String) unless value.is_a?(String)
add_error(I18n.t("#{i18n_key}.name_invalid", name: value)) add_error(I18n.t("#{i18n_key}.name_invalid", name: value))

Datei anzeigen

@ -47,6 +47,7 @@ class CustomWizard::Mapper
@data = params[:data] || {} @data = params[:data] || {}
@user = params[:user] @user = params[:user]
@opts = params[:opts] || {} @opts = params[:opts] || {}
@pro = CustomWizard::Pro.new
end end
def perform def perform
@ -251,7 +252,7 @@ class CustomWizard::Mapper
end end
end end
if opts[:template] if @pro.subscribed? && opts[:template]
template = Liquid::Template.parse(string) template = Liquid::Template.parse(string)
string = template.render(data) string = template.render(data)
end end

Datei anzeigen

@ -1,12 +1,22 @@
# frozen_string_literal: true # frozen_string_literal: true
class CustomWizard::Pro class CustomWizard::Pro
attr_reader :authentication, include ActiveModel::Serialization
:subscription
attr_accessor :authentication,
:subscription
def initialize def initialize
@authentication = CustomWizard::ProAuthentication.new @authentication = CustomWizard::ProAuthentication.new(get_authentication)
@subscription = CustomWizard::ProSubscription.new @subscription = CustomWizard::ProSubscription.new(get_subscription)
end
def authorized?
@authentication.active?
end
def subscribed?
@subscription.active?
end end
def server def server
@ -21,12 +31,8 @@ class CustomWizard::Pro
"custom-wizard" "custom-wizard"
end end
def authorized? def scope
@authentication.active? "discourse-subscription-server:user_subscription"
end
def subscribed?
@subscription.active?
end end
def update_subscription def update_subscription
@ -45,28 +51,35 @@ class CustomWizard::Pro
return false return false
end end
return @subscription.update(data) return false unless data && data.is_a?(Hash)
subscriptions = data[:subscriptions]
if subscriptions.present?
subscription = subscriptions.first
type = subscription[:price_nickname]
@subscription = set_subscription(type)
return true
end
end end
end end
@subscription.destroy remove_subscription
false
end end
def destroy def destroy_subscription
@authentication.destroy remove_subscription
end end
def auth_request(user_id, request_id) def authentication_request(user_id, request_id)
keys = @authentication.generate_keys(user_id, request_id) keys = @authentication.generate_keys(user_id, request_id)
params = { params = {
public_key: keys.public_key, public_key: keys.public_key,
nonce: keys.nonce, nonce: keys.nonce,
client_id: @authentication.client_id, client_id: @authentication.client_id,
auth_redirect: "#{Discourse.base_url}/admin/wizards/pro/authorize/callback", auth_redirect: "#{Discourse.base_url}/admin/wizards/pro/authorize/callback",
application_name: SiteSetting.title, application_name: SiteSetting.title,
scopes: "discourse-subscription-server:user_subscription" scopes: scope
} }
uri = URI.parse("https://#{server}/user-api-key/new") uri = URI.parse("https://#{server}/user-api-key/new")
@ -74,26 +87,24 @@ class CustomWizard::Pro
uri.to_s uri.to_s
end end
def auth_response(request_id, payload) def authentication_response(request_id, payload)
data = @authentication.decrypt_payload(request_id, payload) data = @authentication.decrypt_payload(request_id, payload)
return unless data.is_a?(Hash) && data[:key] && data[:user_id] return unless data.is_a?(Hash) && data[:key] && data[:user_id]
@authentication.update(data)
api_key = data[:key]
user_id = data[:user_id]
user = User.find(user_id)
if user&.admin
@authentication = set_authentication(api_key, user.id)
true
else
false
end
end end
def self.update def destroy_authentication
self.new.update remove_authentication
end
def self.destroy
self.new.destroy
end
def self.generate_request
self.new.generate_request
end
def self.handle_response
self.new.handle_response
end end
def self.subscribed? def self.subscribed?
@ -103,4 +114,56 @@ class CustomWizard::Pro
def self.namespace def self.namespace
"custom_wizard_pro" "custom_wizard_pro"
end end
private
def subscription_db_key
"subscription"
end
def authentication_db_key
"authentication"
end
def get_subscription
raw = PluginStore.get(self.class.namespace, subscription_db_key)
if raw.present?
OpenStruct.new(
type: raw['type'],
updated_at: raw['updated_at']
)
end
end
def remove_subscription
PluginStore.remove(self.class.namespace, subscription_db_key)
end
def set_subscription(type)
PluginStore.set(CustomWizard::Pro.namespace, subscription_db_key, type: type, updated_at: Time.now)
CustomWizard::ProSubscription.new(get_subscription)
end
def get_authentication
raw = PluginStore.get(self.class.namespace, authentication_db_key)
OpenStruct.new(
key: raw && raw['key'],
auth_by: raw && raw['auth_by'],
auth_at: raw && raw['auth_at']
)
end
def set_authentication(key, user_id)
PluginStore.set(self.class.namespace, authentication_db_key,
key: key,
auth_by: user_id,
auth_at: Time.now
)
CustomWizard::ProAuthentication.new(get_authentication)
end
def remove_authentication
PluginStore.remove(self.class.namespace, authentication_db_key)
end
end end

Datei anzeigen

@ -6,12 +6,13 @@ class CustomWizard::ProAuthentication
:auth_at, :auth_at,
:api_key :api_key
def initialize def initialize(auth)
api = get_api_key if auth
@api_key = auth.key
@auth_at = auth.auth_at
@auth_by = auth.auth_by
end
@api_key = api.key
@auth_at = api.auth_at
@auth_by = api.auth_by
@client_id = get_client_id || set_client_id @client_id = get_client_id || set_client_id
end end
@ -19,22 +20,6 @@ class CustomWizard::ProAuthentication
@api_key.present? @api_key.present?
end 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 generate_keys(user_id, request_id) def generate_keys(user_id, request_id)
rsa = OpenSSL::PKey::RSA.generate(2048) rsa = OpenSSL::PKey::RSA.generate(2048)
nonce = SecureRandom.hex(32) nonce = SecureRandom.hex(32)
@ -63,50 +48,15 @@ class CustomWizard::ProAuthentication
data data
end end
private private
def api_key_db_key
"api_key"
end
def api_client_id_db_key
"api_client_id"
end
def keys_db_key def keys_db_key
"keys" "keys"
end end
def get_api_key def client_id_db_key
raw = PluginStore.get(CustomWizard::Pro.namespace, api_key_db_key) "client_id"
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_db_key,
key: key,
auth_by: user_id,
auth_at: Time.now
)
end
def remove
PluginStore.remove(CustomWizard::Pro.namespace, api_key_db_key)
end
def get_client_id
PluginStore.get(CustomWizard::Pro.namespace, api_client_id_db_key)
end
def set_client_id
client_id = SecureRandom.hex(32)
PluginStore.set(CustomWizard::Pro.namespace, api_client_id_db_key, client_id)
client_id
end end
def set_keys(request_id, user_id, rsa, nonce) def set_keys(request_id, user_id, rsa, nonce)
@ -129,4 +79,14 @@ class CustomWizard::ProAuthentication
def delete_keys(request_id) def delete_keys(request_id)
PluginStore.remove(CustomWizard::Pro.namespace, "#{keys_db_key}_#{request_id}") PluginStore.remove(CustomWizard::Pro.namespace, "#{keys_db_key}_#{request_id}")
end end
def get_client_id
PluginStore.get(CustomWizard::Pro.namespace, client_id_db_key)
end
def set_client_id
client_id = SecureRandom.hex(32)
PluginStore.set(CustomWizard::Pro.namespace, client_id_db_key, client_id)
client_id
end
end end

Datei anzeigen

@ -4,12 +4,10 @@ class CustomWizard::ProSubscription
attr_reader :type, attr_reader :type,
:updated_at :updated_at
def initialize def initialize(subscription)
raw = get if subscription
@type = subscription.type
if raw @updated_at = subscription.updated_at
@type = raw['type']
@updated_at = raw['updated_at']
end end
end end
@ -18,40 +16,6 @@ class CustomWizard::ProSubscription
end end
def active? def active?
types.include?(type) && updated_at.to_datetime > (Date.today - 15.minutes).to_datetime types.include?(type) && updated_at.to_datetime > (Time.zone.now - 2.hours).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 destroy
remove
end
private
def key
"custom_wizard_pro_subscription"
end
def set(type)
PluginStore.set(CustomWizard::Pro.namespace, key, type: type, updated_at: Time.now)
end
def get
PluginStore.get(CustomWizard::Pro.namespace, key)
end
def remove
PluginStore.remove(CustomWizard::Pro.namespace, key)
end end
end end

Datei anzeigen

@ -31,6 +31,7 @@ class CustomWizard::TemplateValidator
if data[:actions].present? if data[:actions].present?
data[:actions].each do |action| data[:actions].each do |action|
validate_pro_action(action)
check_required(action, :action) check_required(action, :action)
end end
end end
@ -53,15 +54,31 @@ class CustomWizard::TemplateValidator
def self.pro def self.pro
{ {
step: ['condition'], wizard: {},
field: ['conition'] step: {
condition: 'present',
index: 'conditional'
},
field: {
condition: 'present',
index: 'conditional'
},
action: {
type: %w[
send_message
create_category
create_group
watch_categories
send_to_api
]
}
} }
end end
private private
def check_required(object, type) def check_required(object, type)
CustomWizard::TemplateValidator.required[type].each do |property| self.class.required[type].each do |property|
if object[property].blank? if object[property].blank?
errors.add :base, I18n.t("wizard.validation.required", property: property) errors.add :base, I18n.t("wizard.validation.required", property: property)
end end
@ -69,9 +86,13 @@ class CustomWizard::TemplateValidator
end end
def validate_pro(object, type) def validate_pro(object, type)
CustomWizard::TemplateValidator.required[type].each do |property| self.class.pro[type].each do |property, pro_type|
if object[property].present? && !@pro.subscribed? is_pro = (pro_type === 'present' && object[property].present?) ||
errors.add :base, I18n.t("wizard.validation.pro", property: property) (pro_type === 'conditional' && object[property].is_a?(Hash)) ||
(pro_type.is_a?(Array) && pro_type.includes?(object[property]))
if is_pro && @pro.subscribed?
errors.add :base, I18n.t("wizard.validation.pro", type: type.to_s, property: property)
end end
end end
end end

Datei anzeigen

@ -69,7 +69,7 @@ after_initialize do
../controllers/custom_wizard/realtime_validations.rb ../controllers/custom_wizard/realtime_validations.rb
../jobs/regular/refresh_api_access_token.rb ../jobs/regular/refresh_api_access_token.rb
../jobs/regular/set_after_time_wizard.rb ../jobs/regular/set_after_time_wizard.rb
../jobs/scheduled/update_pro_status.rb ../jobs/scheduled/update_pro_subscription.rb
../lib/custom_wizard/validators/template.rb ../lib/custom_wizard/validators/template.rb
../lib/custom_wizard/validators/update.rb ../lib/custom_wizard/validators/update.rb
../lib/custom_wizard/action_result.rb ../lib/custom_wizard/action_result.rb
@ -111,9 +111,9 @@ after_initialize do
../serializers/custom_wizard/log_serializer.rb ../serializers/custom_wizard/log_serializer.rb
../serializers/custom_wizard/submission_serializer.rb ../serializers/custom_wizard/submission_serializer.rb
../serializers/custom_wizard/realtime_validation/similar_topics_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/authentication_serializer.rb
../serializers/custom_wizard/pro/subscription_serializer.rb ../serializers/custom_wizard/pro/subscription_serializer.rb
../serializers/custom_wizard/pro_serializer.rb
../extensions/extra_locales_controller.rb ../extensions/extra_locales_controller.rb
../extensions/invites_controller.rb ../extensions/invites_controller.rb
../extensions/users_controller.rb ../extensions/users_controller.rb
@ -126,18 +126,6 @@ after_initialize do
Liquid::Template.register_filter(::CustomWizard::LiquidFilter::FirstNonEmpty) Liquid::Template.register_filter(::CustomWizard::LiquidFilter::FirstNonEmpty)
class CustomWizard::UnpermittedOverride < StandardError; end
CustomWizard.constants.each do |class_name|
klass = CustomWizard.const_get(class_name)
next if !klass.is_a?(Class) || klass.superclass.name.to_s.split("::").first == 'CustomWizard'
klass.define_singleton_method(:prepend) { |klass| raise CustomWizard::UnpermittedOverride.new }
klass.define_singleton_method(:include) { |klass| raise CustomWizard::UnpermittedOverride.new }
klass.define_singleton_method(:define_method) { |name, &block| raise CustomWizard::UnpermittedOverride.new }
klass.define_singleton_method(:define_singleton_method) { |name, &block| raise CustomWizard::UnpermittedOverride.new }
end
add_class_method(:wizard, :user_requires_completion?) do |user| add_class_method(:wizard, :user_requires_completion?) do |user|
wizard_result = self.new(user).requires_completion? wizard_result = self.new(user).requires_completion?
return wizard_result if wizard_result return wizard_result if wizard_result

Datei anzeigen

@ -1,26 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class CustomWizard::ProSerializer < ApplicationSerializer class CustomWizard::ProSerializer < ApplicationSerializer
attributes :server, attributes :server
:authentication, has_one :authentication, serializer: CustomWizard::ProAuthenticationSerializer, embed: :objects
:subscription has_one :subscription, serializer: CustomWizard::ProSubscriptionSerializer, embed: :objects
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 end