0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-11-25 18:50:27 +01:00

API data validation and administration improvements

Dieser Commit ist enthalten in:
Angus McLeod 2019-06-03 12:49:54 +10:00
Ursprung 22d1c6fd06
Commit 819c76b3ff
10 geänderte Dateien mit 200 neuen und 107 gelöschten Zeilen

Datei anzeigen

@ -1,8 +1,7 @@
import { ajax } from 'discourse/lib/ajax'; import { ajax } from 'discourse/lib/ajax';
import { popupAjaxError } from 'discourse/lib/ajax-error'; import { popupAjaxError } from 'discourse/lib/ajax-error';
import CustomWizardApi from '../models/custom-wizard-api'; import CustomWizardApi from '../models/custom-wizard-api';
import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; import { default as computed } from 'ember-addons/ember-computed-decorators';
import DiscourseURL from 'discourse/lib/url';
export default Ember.Controller.extend({ export default Ember.Controller.extend({
queryParams: ['refresh_list'], queryParams: ['refresh_list'],
@ -13,10 +12,11 @@ export default Ember.Controller.extend({
isBasicAuth: Ember.computed.equal('api.authType', 'basic'), isBasicAuth: Ember.computed.equal('api.authType', 'basic'),
endpointMethods: ['GET', 'PUT', 'POST', 'PATCH', 'DELETE'], endpointMethods: ['GET', 'PUT', 'POST', 'PATCH', 'DELETE'],
showRemove: Ember.computed.not('isNew'), showRemove: Ember.computed.not('isNew'),
responseIcon: null,
@computed('saveDisabled', 'api.authType', 'api.authUrl') @computed('saveDisabled', 'api.authType', 'api.authUrl', 'api.clientId', 'api.clientSecret')
authDisabled(saveDisabled, authType, authUrl) { authDisabled(saveDisabled, authType, authUrl, clientId, clientSecret) {
return saveDisabled || !authType || !authUrl; return saveDisabled || !authType || !authUrl || !clientId || !clientSecret;
}, },
@computed('api.name', 'api.authType') @computed('api.name', 'api.authType')
@ -24,15 +24,6 @@ export default Ember.Controller.extend({
return !name || !authType; return !name || !authType;
}, },
@observes('api.title')
titleWatcher() {
const title = this.get('api.title');
if (this.get('originalTitle')) {
this.set('originalTitle', title);
}
},
actions: { actions: {
addParam() { addParam() {
this.get('api.authParams').pushObject({}); this.get('api.authParams').pushObject({});
@ -76,6 +67,7 @@ export default Ember.Controller.extend({
const name = api.name; const name = api.name;
const authType = api.authType; const authType = api.authType;
let refreshList = false; let refreshList = false;
let error;
if (!name || !authType) return; if (!name || !authType) return;
@ -85,7 +77,9 @@ export default Ember.Controller.extend({
if (api.title) data['title'] = api.title; if (api.title) data['title'] = api.title;
if (api.get('isNew') || (api.title !== this.get('originalTitle'))) { const originalTitle = this.get('api.originalTitle');
console.log(api, originalTitle);
if (api.get('isNew') || (originalTitle && (api.title !== originalTitle))) {
refreshList = true; refreshList = true;
} }
@ -93,29 +87,47 @@ export default Ember.Controller.extend({
data['new'] = true; data['new'] = true;
}; };
let requiredParams;
if (authType === 'oauth') { if (authType === 'oauth') {
data['auth_url'] = api.authUrl; requiredParams = ['authUrl', 'tokenUrl', 'clientId', 'clientSecret'];
data['client_id'] = api.clientId;
data['client_secret'] = api.clientSecret;
let params = api.authParams;
if (params) {
data['auth_params'] = JSON.stringify(params);
}
data['token_url'] = api.tokenUrl;
} else if (authType === 'basic') { } else if (authType === 'basic') {
data['username'] = api.username; requiredParams = ['username', 'password'];
data['password'] = api.password; }
for (let rp of requiredParams) {
if (!api[rp]) {
let key = rp.replace('auth', '');
error = `${I18n.t(`admin.wizard.api.auth.${key.underscore()}`)} is required for ${authType}`;
break;
}
data[rp.underscore()] = api[rp];
}
const params = api.authParams;
if (params.length) {
data['auth_params'] = JSON.stringify(params);
} }
const endpoints = api.endpoints; const endpoints = api.endpoints;
if (endpoints.length) { if (endpoints.length) {
for (let e of endpoints) {
if (!e.name) {
error = 'Every endpoint must have a name';
break;
}
}
data['endpoints'] = JSON.stringify(endpoints); data['endpoints'] = JSON.stringify(endpoints);
} }
if (error) {
this.set('error', error);
setTimeout(() => {
this.set('error', '');
}, 6000);
return;
}
this.set('updating', true); this.set('updating', true);
ajax(`/admin/wizards/apis/${name.underscore()}`, { ajax(`/admin/wizards/apis/${name.underscore()}`, {
@ -130,7 +142,10 @@ export default Ember.Controller.extend({
}); });
} else { } else {
this.set('api', CustomWizardApi.create(result.api)); this.set('api', CustomWizardApi.create(result.api));
this.set('responseIcon', 'check');
} }
} else {
this.set('responseIcon', 'times');
} }
}).finally(() => this.set('updating', false)); }).finally(() => this.set('updating', false));
}, },
@ -146,7 +161,9 @@ export default Ember.Controller.extend({
}).catch(popupAjaxError) }).catch(popupAjaxError)
.then(result => { .then(result => {
if (result.success) { if (result.success) {
DiscourseURL.routeTo('/admin/wizards/apis?refresh=true'); this.transitionToRoute('adminWizardsApis').then(() => {
this.send('refreshModel');
});
} }
}).finally(() => this.set('updating', false)); }).finally(() => this.set('updating', false));
} }

Datei anzeigen

@ -19,6 +19,7 @@ CustomWizardApi.reopenClass({
api.setProperties({ api.setProperties({
name: params.name, name: params.name,
title: params.title, title: params.title,
originalTitle: params.title,
authType: authorization.auth_type, authType: authorization.auth_type,
authUrl: authorization.auth_url, authUrl: authorization.auth_url,
tokenUrl: authorization.token_url, tokenUrl: authorization.token_url,

Datei anzeigen

@ -1,23 +1,11 @@
<div class="wizard-api-header page"> <div class="wizard-api-header page">
<div class="metadata">
<div class="title">
<label>{{i18n 'admin.wizard.api.title'}}</label>
{{input value=api.title placeholder=(i18n 'admin.wizard.api.title_placeholder')}}
</div>
<div class="name">
{{#if api.isNew}}
<label>{{i18n 'admin.wizard.api.name'}}</label>
{{input value=api.name placeholder=(i18n 'admin.wizard.api.name_placeholder')}}
{{else}}
{{api.name}}
{{/if}}
</div>
</div>
<div class='buttons'> <div class='buttons'>
{{#if updating}} {{#if updating}}
{{loading-spinner size="small"}} {{loading-spinner size="small"}}
{{else}}
{{#if responseIcon}}
{{d-icon responseIcon}}
{{/if}}
{{/if}} {{/if}}
{{d-button label="admin.wizard.api.save" action="save" class="btn-primary" disabled=saveDisabled}} {{d-button label="admin.wizard.api.save" action="save" class="btn-primary" disabled=saveDisabled}}
@ -25,15 +13,41 @@
{{#if showRemove}} {{#if showRemove}}
{{d-button action="remove" label="admin.wizard.api.remove"}} {{d-button action="remove" label="admin.wizard.api.remove"}}
{{/if}} {{/if}}
{{#if error}}
<div class="error">
{{error}}
</div>
{{/if}}
</div>
<div class="wizard-header">
{{#if api.isNew}}
{{i18n 'admin.wizard.api.new'}}
{{else}}
{{api.title}}
{{/if}}
</div>
<div class="metadata">
<div class="title">
<label>{{i18n 'admin.wizard.api.title'}}</label>
{{input value=api.title placeholder=(i18n 'admin.wizard.api.title_placeholder')}}
</div>
<div class="name">
<label>{{i18n 'admin.wizard.api.name'}}</label>
{{#if api.isNew}}
{{input value=api.name placeholder=(i18n 'admin.wizard.api.name_placeholder')}}
{{else}}
{{api.name}}
{{/if}}
</div>
</div> </div>
</div> </div>
<div class="wizard-api-header"> <div class="wizard-api-header">
<div class="wizard-header"> <div class="buttons">
{{i18n 'admin.wizard.api.auth.label'}}
</div>
<div class="actions">
{{#if isOauth}} {{#if isOauth}}
{{d-button label="admin.wizard.api.auth.btn" {{d-button label="admin.wizard.api.auth.btn"
action="authorize" action="authorize"
@ -41,6 +55,10 @@
class="btn-primary"}} class="btn-primary"}}
{{/if}} {{/if}}
</div> </div>
<div class="wizard-header">
{{i18n 'admin.wizard.api.auth.label'}}
</div>
</div> </div>
<div class="wizard-api-authentication"> <div class="wizard-api-authentication">
@ -198,9 +216,20 @@
{{#each api.endpoints as |endpoint|}} {{#each api.endpoints as |endpoint|}}
<li> <li>
<div class="endpoint"> <div class="endpoint">
{{combo-box content=endpointMethods value=endpoint.method none="admin.wizard.api.endpoint.method"}} <div class="endpoint-">
{{input value=endpoint.url placeholder=(i18n 'admin.wizard.api.endpoint.url') class='endpoint-url'}} {{input value=endpoint.name
{{d-button action='removeEndpoint' actionParam=endpoint icon='times' class='remove-endpoint'}} placeholder=(i18n 'admin.wizard.api.endpoint.name')}}
{{combo-box content=endpointMethods
value=endpoint.method
none="admin.wizard.api.endpoint.method"}}
{{input value=endpoint.url
placeholder=(i18n 'admin.wizard.api.endpoint.url')
class='endpoint-url'}}
{{d-button action='removeEndpoint'
actionParam=endpoint
icon='times'
class='remove-endpoint'}}
</div>
</div> </div>
</li> </li>
{{/each}} {{/each}}

Datei anzeigen

@ -316,15 +316,34 @@
.metadata .title input { .metadata .title input {
width: 400px; width: 400px;
} }
.buttons {
text-align: right;
vertical-align: middle;
> .d-icon, > .spinner {
margin-right: 7px;
}
.error {
margin-top: 10px;
color: $danger;
}
}
} }
.wizard-api-header { .wizard-api-header {
display: flex;
justify-content: space-between;
&.page { &.page {
margin-bottom: 20px; margin-bottom: 20px;
} }
.buttons {
float: right;
}
.wizard-header {
overflow: hidden;
}
} }
.wizard-api-authentication { .wizard-api-authentication {
@ -388,12 +407,18 @@
.combo-box { .combo-box {
width: 200px; width: 200px;
margin-right: 20px; margin-right: 10px;
margin-top: -2px;
width: 150px;
}
input {
margin: 0;
margin-right: 10px;
} }
.endpoint-url { .endpoint-url {
margin: 0; width: 300px;
width: 450px;
} }
.remove-endpoint { .remove-endpoint {

Datei anzeigen

@ -170,6 +170,7 @@ en:
endpoint: endpoint:
label: "Endpoints" label: "Endpoints"
add: "Add endpoint" add: "Add endpoint"
name: "Endpoint name"
method: "Select a method" method: "Select a method"
url: "Enter a url" url: "Enter a url"

Datei anzeigen

@ -46,7 +46,7 @@ class CustomWizard::ApiController < ::ApplicationController
render json: success_json.merge( render json: success_json.merge(
api: CustomWizard::ApiSerializer.new( api: CustomWizard::ApiSerializer.new(
CustomWizard::Api.new(api_params[:name]), CustomWizard::Api.get(api_params[:name]),
root: false root: false
) )
) )

Datei anzeigen

@ -6,7 +6,9 @@ class CustomWizard::Api
def initialize(name, data={}) def initialize(name, data={})
@name = name @name = name
@title = data['title'] data.each do |k, v|
self.send "#{k}=", v if self.respond_to?(k)
end
end end
def self.set(name, data) def self.set(name, data)

Datei anzeigen

@ -3,8 +3,8 @@ require 'excon'
class CustomWizard::Api::Authorization class CustomWizard::Api::Authorization
include ActiveModel::SerializerSupport include ActiveModel::SerializerSupport
attr_accessor :authorized, attr_accessor :api_name,
:name, :authorized,
:auth_type, :auth_type,
:auth_url, :auth_url,
:token_url, :token_url,
@ -19,15 +19,11 @@ class CustomWizard::Api::Authorization
:username, :username,
:password :password
def initialize(name, data, opts = {}) def initialize(api_name, data={})
unless opts[:data_only] @api_name = api_name
@name = name
end
if data = data.is_a?(String) ? ::JSON.parse(data) : data data.each do |k, v|
data.each do |k, v| self.send "#{k}=", v if self.respond_to?(k)
self.send "#{k}=", v if self.respond_to?(k)
end
end end
end end
@ -35,25 +31,32 @@ class CustomWizard::Api::Authorization
@authorized ||= @access_token && @token_expires_at.to_datetime > Time.now @authorized ||= @access_token && @token_expires_at.to_datetime > Time.now
end end
def self.set(name, data = {}) def self.set(api_name, new_data = {})
record = self.get(name, data_only: true) data = self.get(api_name, data_only: true) || {}
data.each do |k, v| new_data.each do |k, v|
record.send "#{k}=", v if record.respond_to?(k) data[k.to_sym] = v
end end
PluginStore.set("custom_wizard_api_#{name}", 'authorization', record.as_json) PluginStore.set("custom_wizard_api_#{api_name}", 'authorization', data)
self.get(name) self.get(api_name)
end end
def self.get(name, opts = {}) def self.get(api_name, opts = {})
data = PluginStore.get("custom_wizard_api_#{name}", 'authorization') if data = PluginStore.get("custom_wizard_api_#{api_name}", 'authorization')
self.new(name, data, opts) if opts[:data_only]
data
else
self.new(api_name, data)
end
else
nil
end
end end
def self.remove(name) def self.remove(api_name)
PluginStore.remove("custom_wizard_api_#{name}", "authorization") PluginStore.remove("custom_wizard_api_#{api_name}", "authorization")
end end
def self.get_header_authorization_string(name) def self.get_header_authorization_string(name)

Datei anzeigen

@ -2,49 +2,59 @@ class CustomWizard::Api::Endpoint
include ActiveModel::SerializerSupport include ActiveModel::SerializerSupport
attr_accessor :id, attr_accessor :id,
:name,
:api_name,
:method, :method,
:url :url
def initialize(name, params) def initialize(api_name, data={})
@name = name @api_name = api_name
if data = params.is_a?(String) ? ::JSON.parse(params) : params
data.each do |k, v|
self.send "#{k}=", v if self.respond_to?(k)
end
end
end
def self.set(name, data)
model = data[:endpoint_id] ? self.get(name, data[:endpoint_id]) : {}
endpoint_id = model[:endpoint_id] || SecureRandom.hex(8)
data.each do |k, v| data.each do |k, v|
model.send "#{k}=", v if model.respond_to?(k) self.send "#{k}=", v if self.respond_to?(k)
end
end
def self.set(api_name, new_data)
data = new_data[:endpoint_id] ? self.get(api_name, new_data[:endpoint_id], data_only: true) : {}
endpoint_id = new_data[:endpoint_id] || SecureRandom.hex(3)
new_data.each do |k, v|
data[k.to_sym] = v
end end
PluginStore.set("custom_wizard_api_#{name}", "endpoint_#{endpoint_id}", model.as_json) PluginStore.set("custom_wizard_api_#{api_name}", "endpoint_#{endpoint_id}", data)
self.get(name) self.get(api_name, endpoint_id)
end end
def self.get(name, endpoint_id) def self.get(api_name, endpoint_id, opts={})
return nil if !endpoint_id return nil if !endpoint_id
data = PluginStore.get("custom_wizard_api_#{name}", "endpoint_#{endpoint_id}")
data[:id] = endpoint_id if data = PluginStore.get("custom_wizard_api_#{api_name}", "endpoint_#{endpoint_id}")
self.new(name, data) data[:id] = endpoint_id
if opts[:data_only]
data
else
self.new(api_name, data)
end
else
nil
end
end end
def self.remove(name) def self.remove(api_name)
PluginStoreRow.where("plugin_name = 'custom_wizard_api_#{name}' AND key LIKE 'endpoint_%'").destroy_all PluginStoreRow.where("plugin_name = 'custom_wizard_api_#{api_name}' AND key LIKE 'endpoint_%'").destroy_all
end end
def self.list def self.list
PluginStoreRow.where("plugin_name LIKE 'custom_wizard_api_%' AND key LIKE 'endpoint_%'") PluginStoreRow.where("plugin_name LIKE 'custom_wizard_api_%' AND key LIKE 'endpoint_%'")
.map do |record| .map do |record|
name = record['plugin_name'].sub("custom_wizard_api_", "") api_name = record['plugin_name'].sub("custom_wizard_api_", "")
data = ::JSON.parse(record['value']) data = ::JSON.parse(record['value'])
data[:id] = record['key'].split('_').last data[:id] = record['key'].split('_').last
self.new(name, data) self.new(api_name, data)
end end
end end
end end

Datei anzeigen

@ -1,5 +1,10 @@
class CustomWizard::Api::EndpointSerializer < ApplicationSerializer class CustomWizard::Api::EndpointSerializer < ApplicationSerializer
attributes :id, attributes :id,
:type, :name,
:method,
:url :url
def method
object.send('method')
end
end end