Improve PRO feature approach
Dieser Commit ist enthalten in:
Ursprung
a810155f91
Commit
6b1e7568c1
22 geänderte Dateien mit 331 neuen und 314 gelöschten Zeilen
|
@ -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) => {
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -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}}
|
|
@ -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>
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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."
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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'],
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
|
16
plugin.rb
16
plugin.rb
|
@ -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
|
||||||
|
|
|
@ -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
|
Laden …
In neuem Issue referenzieren