1
0
Fork 0

Update wizard api CRUD

- New api metadata model
- New api id system
- Minor UI updates
Dieser Commit ist enthalten in:
Angus McLeod 2019-06-02 20:54:31 +10:00
Ursprung 6345083c4d
Commit f331f80cbb
19 geänderte Dateien mit 446 neuen und 208 gelöschten Zeilen

Datei anzeigen

@ -1,7 +1,7 @@
# Uncomment tests runner when tests are added. # Uncomment tests runner when tests are added.
sudo: required sudo: required
#services: #names:
#- docker #- docker
before_install: before_install:

Datei anzeigen

@ -1,13 +1,32 @@
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 DiscourseURL from 'discourse/lib/url';
export default Ember.Controller.extend({ export default Ember.Controller.extend({
queryParams: ['refresh_list'],
loadingSubscriptions: false, loadingSubscriptions: false,
notAuthorized: Ember.computed.not('api.authorized'), notAuthorized: Ember.computed.not('api.authorized'),
authorizationTypes: ['oauth', 'basic'], authorizationTypes: ['oauth', 'basic'],
isOauth: Ember.computed.equal('api.authType', 'oauth'), isOauth: Ember.computed.equal('api.authType', 'oauth'),
endpointMethods: ['GET', 'PUT', 'POST', 'PATCH', 'DELETE'], endpointMethods: ['GET', 'PUT', 'POST', 'PATCH', 'DELETE'],
saveDisabled: Ember.computed.empty('api.name'),
showRemove: Ember.computed.not('isNew'),
@computed('saveDisabled', 'authType', 'authUrl')
authDisabled(saveDisabled, authType, authUrl) {
return saveDisabled || !authType || !authUrl;
},
@observes('api.title')
titleWatcher() {
const title = this.get('api.title');
if (this.get('originalTitle')) {
this.set('originalTitle', title);
}
},
actions: { actions: {
addParam() { addParam() {
@ -48,12 +67,25 @@ export default Ember.Controller.extend({
save() { save() {
const api = this.get('api'); const api = this.get('api');
const service = api.service; const name = api.name;
let refreshList = false;
let data = {}; if (!name ) return;
data['auth_type'] = api.authType; let data = {
data['auth_url'] = api.authUrl; title: api.title
};
if (this.get('isNew') || (api.title !== this.get('originalTitle'))) {
refreshList = true;
}
if (api.get('isNew')) {
data['new'] = true;
};
if (api.authType) data['auth_type'] = api.authType;
if (api.authUrl) data['auth_url'] = api.authUrl;
if (data.auth_type === 'oauth') { if (data.auth_type === 'oauth') {
data['client_id'] = api.clientId; data['client_id'] = api.clientId;
@ -72,23 +104,43 @@ export default Ember.Controller.extend({
} }
const endpoints = api.endpoints; const endpoints = api.endpoints;
if (endpoints != undefined) { if (endpoints.length) {
if (endpoints.length) { data['endpoints'] = JSON.stringify(endpoints);
data['endpoints'] = JSON.stringify(endpoints);
}
} }
this.set('savingApi', true); this.set('updating', true);
ajax(`/admin/wizards/apis/${service}`, { ajax(`/admin/wizards/apis/${name.underscore()}`, {
type: 'PUT', type: 'PUT',
data data
}).catch(popupAjaxError) }).catch(popupAjaxError)
.then(result => { .then(result => {
if (result.success) { if (result.success) {
this.set('api', CustomWizardApi.create(result.api)); if (refreshList) {
this.transitionToRoute('adminWizardsApi', result.api.name.dasherize()).then(() => {
this.send('refreshModel');
});
} else {
this.set('api', CustomWizardApi.create(result.api));
}
} }
}).finally(() => this.set('savingApi', false)); }).finally(() => this.set('updating', false));
},
remove() {
const name = this.get('api.name');
if (!name) return;
this.set('updating', true);
ajax(`/admin/wizards/apis/${name.underscore()}`, {
type: 'DELETE'
}).catch(popupAjaxError)
.then(result => {
if (result.success) {
DiscourseURL.routeTo('/admin/wizards/apis?refresh=true');
}
}).finally(() => this.set('updating', false));
} }
} }
}); });

Datei anzeigen

@ -0,0 +1,3 @@
export default Ember.Controller.extend({
queryParams: ['refresh']
});

Datei anzeigen

@ -9,7 +9,7 @@ export default {
this.route('adminWizardSubmissions', { path: '/:wizard_id', resetNamespace: true }); this.route('adminWizardSubmissions', { path: '/:wizard_id', resetNamespace: true });
}); });
this.route('adminWizardsApis', { path: '/apis', resetNamespace: true }, function() { this.route('adminWizardsApis', { path: '/apis', resetNamespace: true }, function() {
this.route('adminWizardsApi', { path: '/:service', resetNamespace: true }); this.route('adminWizardsApi', { path: '/:name', resetNamespace: true });
}); });
}); });
} }

Datei anzeigen

@ -2,10 +2,11 @@ import { ajax } from 'discourse/lib/ajax';
import { default as computed } from 'ember-addons/ember-computed-decorators'; import { default as computed } from 'ember-addons/ember-computed-decorators';
const CustomWizardApi = Discourse.Model.extend({ const CustomWizardApi = Discourse.Model.extend({
@computed('service') @computed('name')
redirectUri(service) { redirectUri(name) {
let nameParam = name.toString().dasherize();
const baseUrl = location.protocol+'//'+location.hostname+(location.port ? ':'+location.port: ''); const baseUrl = location.protocol+'//'+location.hostname+(location.port ? ':'+location.port: '');
return baseUrl + `/admin/wizards/apis/${service}/redirect`; return baseUrl + `/admin/wizards/apis/${nameParam}/redirect`;
} }
}); });
@ -16,7 +17,8 @@ CustomWizardApi.reopenClass({
const endpoints = params.endpoints; const endpoints = params.endpoints;
api.setProperties({ api.setProperties({
service: params.service, name: params.name,
title: 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,
@ -29,14 +31,15 @@ CustomWizardApi.reopenClass({
code: authorization.code, code: authorization.code,
tokenExpiresAt: authorization.token_expires_at, tokenExpiresAt: authorization.token_expires_at,
tokenRefreshAt: authorization.token_refresh_at, tokenRefreshAt: authorization.token_refresh_at,
endpoints: Ember.A(endpoints) endpoints: Ember.A(endpoints),
isNew: params.isNew
}); });
return api; return api;
}, },
find(service) { find(name) {
return ajax(`/admin/wizards/apis/${service}`, { return ajax(`/admin/wizards/apis/${name}`, {
type: 'GET' type: 'GET'
}).then(result => { }).then(result => {
return CustomWizardApi.create(result); return CustomWizardApi.create(result);

Datei anzeigen

@ -1,11 +1,17 @@
import CustomWizardApi from '../models/custom-wizard-api'; import CustomWizardApi from '../models/custom-wizard-api';
export default Discourse.Route.extend({ export default Discourse.Route.extend({
queryParams: {
refresh_list: {
refreshModel: true
}
},
model(params) { model(params) {
if (params.service === 'new') { if (params.name === 'new') {
return CustomWizardApi.create(); return CustomWizardApi.create({ isNew: true });
} else { } else {
return CustomWizardApi.find(params.service); return CustomWizardApi.find(params.name);
} }
}, },

Datei anzeigen

@ -5,7 +5,27 @@ export default Discourse.Route.extend({
return CustomWizardApi.list(); return CustomWizardApi.list();
}, },
afterModel(model) {
const apiParams = this.paramsFor('admin-wizards-api');
if (model.length) {
if (!apiParams.name) {
this.transitionTo('adminWizardsApi', model[0].name.dasherize());
} else {
return;
}
} else {
this.transitionTo('adminWizardsApi', 'new');
}
},
setupController(controller, model){ setupController(controller, model){
controller.set("model", model); controller.set("model", model);
},
actions: {
refreshModel() {
this.refresh();
}
} }
}); });

Datei anzeigen

@ -1,51 +1,77 @@
<div class="wizard-api-header"> <div class="wizard-api-header page">
<div class="service"> <div class="metadata">
{{input value=api.service placeholder=(i18n 'admin.wizard.api.service')}} <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>
<div class='buttons'> <div class='buttons'>
{{#if savingApi}} {{#if updating}}
{{loading-spinner size="small"}} {{loading-spinner size="small"}}
{{/if}} {{/if}}
{{d-button label="admin.wizard.api.save" action="save" class="btn-primary"}}
{{d-button action="removeApi" label="admin.wizard.api.remove"}} {{d-button label="admin.wizard.api.save" action="save" class="btn-primary" disabled=saveDisabled}}
{{#if showRemove}}
{{d-button action="remove" label="admin.wizard.api.remove"}}
{{/if}}
</div> </div>
</div> </div>
<div class="wizard-header"> <div class="wizard-api-header">
{{i18n 'admin.wizard.api.auth'}} <div class="wizard-header">
{{i18n 'admin.wizard.api.auth.label'}}
</div>
<div class="actions">
{{d-button label="admin.wizard.api.auth.btn" action="authorize" disabled=authDisabled class="btn-primary"}}
</div>
</div> </div>
<div class="wizard-api-authentication"> <div class="wizard-api-authentication">
<div class="settings"> <div class="settings">
<div class="wizard-header medium"> <div class="wizard-header medium">
{{i18n 'admin.wizard.api.auth_settings'}} {{i18n 'admin.wizard.api.auth.settings'}}
</div> </div>
<div class="control-group"> {{#if api.name}}
<div class="control-label"> <div class="control-group redirect-uri">
<span>{{i18n 'admin.wizard.api.redirect_uri'}}</span> <div class="control-label">
<span>{{api.redirectUri}}</span> <label>{{i18n 'admin.wizard.api.auth.redirect_uri'}}</label>
<div class="controls">
{{api.redirectUri}}
</div>
</div>
</div> </div>
</div> {{/if}}
<div class="control-group"> <div class="control-group auth-type">
<label>{{i18n 'admin.wizard.api.auth_type'}}</label> <label>{{i18n 'admin.wizard.api.auth.type'}}</label>
<div class="controls"> <div class="controls">
{{combo-box value=api.authType content=authorizationTypes none='admin.wizard.api.auth_type_none'}} {{combo-box value=api.authType content=authorizationTypes none='admin.wizard.api.auth.type_none'}}
</div> </div>
</div> </div>
<div class="control-group"> <div class="control-group">
<label>{{i18n 'admin.wizard.api.auth_url'}}</label> <label>{{i18n 'admin.wizard.api.auth.url'}}</label>
<div class="controls"> <div class="controls">
{{input value=api.authUrl}} {{input value=api.authUrl}}
</div> </div>
</div> </div>
<div class="control-group"> <div class="control-group">
<label>{{i18n 'admin.wizard.api.token_url'}}</label> <label>{{i18n 'admin.wizard.api.auth.token_url'}}</label>
<div class="controls"> <div class="controls">
{{input value=api.tokenUrl}} {{input value=api.tokenUrl}}
</div> </div>
@ -53,85 +79,82 @@
{{#if isOauth}} {{#if isOauth}}
<div class="control-group"> <div class="control-group">
<label>{{i18n 'admin.wizard.api.client_id'}}</label> <label>{{i18n 'admin.wizard.api.auth.client_id'}}</label>
<div class="controls"> <div class="controls">
{{input value=api.clientId}} {{input value=api.clientId}}
</div> </div>
</div> </div>
<div class="control-group"> <div class="control-group">
<label>{{i18n 'admin.wizard.api.client_secret'}}</label> <label>{{i18n 'admin.wizard.api.auth.client_secret'}}</label>
<div class="controls"> <div class="controls">
{{input value=api.clientSecret}} {{input value=api.clientSecret}}
</div> </div>
</div> </div>
<div class="control-group"> <div class="control-group">
<label>{{i18n 'admin.wizard.api.params'}}</label> <label>{{i18n 'admin.wizard.api.auth.params.label'}}</label>
<div class="controls"> <div class="controls">
{{#each api.authParams as |param|}} {{#each api.authParams as |param|}}
<div class="param"> <div class="param">
{{input value=param.key placeholder=(i18n 'admin.wizard.api.param_key')}} {{input value=param.key placeholder=(i18n 'admin.wizard.api.auth.params.key')}}
{{input value=param.value placeholder=(i18n 'admin.wizard.api.param_value')}} {{input value=param.value placeholder=(i18n 'admin.wizard.api.auth.params.value')}}
{{d-button action='removeParam' actionParam=param icon='times'}} {{d-button action='removeParam' actionParam=param icon='times'}}
</div> </div>
{{/each}} {{/each}}
{{d-button label='admin.wizard.api.param_new' icon='plus' action='addParam'}} {{d-button label='admin.wizard.api.auth.params.new' icon='plus' action='addParam'}}
</div> </div>
</div> </div>
{{/if}} {{/if}}
<div class="actions">
{{d-button label="admin.wizard.api.authorize" action="authorize"}}
</div>
</div> </div>
<div class="status"> <div class="status">
<div class="wizard-header medium">
{{i18n 'admin.wizard.api.auth_status'}}
</div>
<div class="authorization"> <div class="authorization">
{{#if api.authorized}} {{#if api.authorized}}
<span class="authorization-indicator authorized"></span> <span class="authorization-indicator authorized"></span>
<span>{{i18n "admin.wizard.api.authorized"}}</span> <span>{{i18n "admin.wizard.api.status.authorized"}}</span>
{{else}} {{else}}
<span class="authorization-indicator not-authorized"></span> <span class="authorization-indicator not-authorized"></span>
<span>{{i18n "admin.wizard.api.not_authorized"}}</span> <span>{{i18n "admin.wizard.api.status.not_authorized"}}</span>
{{/if}} {{/if}}
</div> </div>
<div class="wizard-header medium">
{{i18n 'admin.wizard.api.status.label'}}
</div>
<div class="control-group"> <div class="control-group">
<label>Access Token:</label> <label>{{i18n 'admin.wizard.api.status.code'}}</label>
<div class="controls">
{{api.code}}
</div>
</div>
<div class="control-group">
<label>{{i18n 'admin.wizard.api.status.access_token'}}</label>
<div class="controls"> <div class="controls">
{{api.accessToken}} {{api.accessToken}}
</div> </div>
</div> </div>
<div class="control-group"> <div class="control-group">
<label>Expires At:</label> <label>{{i18n 'admin.wizard.api.status.refresh_token'}}</label>
<div class="controls">
{{api.tokenExpiresAt}}
</div>
</div>
<div class="control-group">
<label>Refresh At:</label>
<div class="controls">
{{api.tokenRefreshAt}}
</div>
</div>
<div class="control-group">
<label>Refresh Token:</label>
<div class="controls"> <div class="controls">
{{api.refreshToken}} {{api.refreshToken}}
</div> </div>
</div> </div>
<div class="control-group"> <div class="control-group">
<label>Code:</label> <label>{{i18n 'admin.wizard.api.status.expires_at'}}</label>
<div class="controls"> <div class="controls">
{{api.code}} {{api.tokenExpiresAt}}
</div>
</div>
<div class="control-group">
<label>{{i18n 'admin.wizard.api.status.refresh_at'}}</label>
<div class="controls">
{{api.tokenRefreshAt}}
</div> </div>
</div> </div>
</div> </div>
@ -144,17 +167,19 @@
<div class="wizard-api-endpoints"> <div class="wizard-api-endpoints">
{{d-button action='addEndpoint' label='admin.wizard.api.endpoint.add' icon='plus'}} {{d-button action='addEndpoint' label='admin.wizard.api.endpoint.add' icon='plus'}}
<div class="endpoint-list"> {{#if api.endpoints}}
<ul> <div class="endpoint-list">
{{#each api.endpoints as |endpoint|}} <ul>
<li> {{#each api.endpoints as |endpoint|}}
<div class="endpoint"> <li>
{{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'}} {{combo-box content=endpointMethods value=endpoint.method none="admin.wizard.api.endpoint.method"}}
{{d-button action='removeEndpoint' actionParam=endpoint icon='times' class='remove-endpoint'}} {{input value=endpoint.url placeholder=(i18n 'admin.wizard.api.endpoint.url') class='endpoint-url'}}
</div> {{d-button action='removeEndpoint' actionParam=endpoint icon='times' class='remove-endpoint'}}
</li> </div>
{{/each}} </li>
</ul> {{/each}}
</div> </ul>
</div>
{{/if}}
</div> </div>

Datei anzeigen

@ -3,7 +3,13 @@
<ul> <ul>
{{#each model as |api|}} {{#each model as |api|}}
<li> <li>
{{#link-to "adminWizardsApi" api.service}}{{api.service}}{{/link-to}} {{#link-to "adminWizardsApi" (dasherize api.name)}}
{{#if api.title}}
{{api.title}}
{{else}}
{{api.name}}
{{/if}}
{{/link-to}}
</li> </li>
{{/each}} {{/each}}
</ul> </ul>

Datei anzeigen

@ -308,12 +308,23 @@
.content-list { .content-list {
margin-right: 20px; margin-right: 20px;
} }
.new-api {
margin-top: 20px;
}
.metadata .title input {
width: 400px;
}
} }
.wizard-api-header { .wizard-api-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-bottom: 20px;
&.page {
margin-bottom: 20px;
}
} }
.wizard-api-authentication { .wizard-api-authentication {
@ -324,8 +335,37 @@
.settings { .settings {
border-right: 1px solid #333; border-right: 1px solid #333;
margin-right: 10px; margin-right: 20px;
padding-right: 20px; padding-right: 20px;
width: 50%;
max-width: 50%;
}
.redirect-uri .controls {
word-break: break-all;
}
.auth-type .select-kit {
min-width: 210px;
width: 210px;
margin-bottom: 10px;
}
.status {
width: 50%;
max-width: 50%;
.wizard-header {
overflow: hidden;
}
.authorization {
float: right;
}
.control-group {
margin-bottom: 15px;
}
} }
} }

Datei anzeigen

@ -127,36 +127,47 @@ en:
wizard_field: "Wizard Field" wizard_field: "Wizard Field"
user_field: "User Field" user_field: "User Field"
api: api:
label: "API"
nav_label: 'APIs' nav_label: 'APIs'
new: 'New Api' new: 'New Api'
name: "Name (can't be changed)"
auth: "Authentication" name_placeholder: 'Underscored'
auth_settings: "Settings" title: 'Title'
auth_status: "Status" title_placeholder: 'Display name'
service: 'Api Service'
redirect_uri: "Redirect Uri"
auth_type: 'Authorization Type'
auth_type_none: 'Select a type'
token_url: "Token Url"
auth_url: 'Authorization Url'
client_id: 'Client Id'
client_secret: 'Client Secret'
status: "Status"
authorized: 'Authorized'
not_authorized: "Not Authorized"
params: 'Params'
param_new: 'New Param'
param_key: 'Param Key'
param_value: 'Param Value'
remove: 'Delete' remove: 'Delete'
authorize: 'Authorize'
save: "Save" save: "Save"
auth:
label: "Authorization"
btn: 'Authorize'
settings: "Settings"
status: "Status"
redirect_uri: "Redirect url"
type: 'Type'
type_none: 'Select a type'
url: "Authorization url"
token_url: "Token url"
client_id: 'Client id'
client_secret: 'Client secret'
params:
label: 'Params'
new: 'New param'
key: 'key'
value: 'value'
status:
label: "Status"
authorized: 'Authorized'
not_authorized: "Not authorized"
code: "Code"
access_token: "Access token"
refresh_token: "Refresh token"
expires_at: "Expires at"
refresh_at: "Refresh at"
endpoint: endpoint:
label: "Endpoints" label: "Endpoints"
add: "Add Endpoint" add: "Add endpoint"
method: "Select a method" method: "Select a method"
url: "Enter a url" url: "Enter a url"

Datei anzeigen

@ -11,55 +11,75 @@ class CustomWizard::ApiController < ::ApplicationController
CustomWizard::Api.list, CustomWizard::Api.list,
each_serializer: CustomWizard::BasicApiSerializer each_serializer: CustomWizard::BasicApiSerializer
) )
render json: MultiJson.dump(serializer) render json: MultiJson.dump(serializer)
end end
def find def find
params.require(:service) render_serialized(CustomWizard::Api.get(api_params[:name]), CustomWizard::ApiSerializer, root: false)
render_serialized(CustomWizard::Api.new(params[:service]), CustomWizard::ApiSerializer, root: false)
end end
def save def save
byebug current = CustomWizard::Api.get(api_params[:name])
params.require(:service)
service = params.permit(:service)
auth_data = params[:auth_params]
endpoints_data = params[:endpoints]
service_auth_data = JSON.parse(auth_data) if !auth_data.nil? if api_params[:new] && current
service_endpoints = JSON.parse(endpoints_data) if !endpoints_data.nil? raise Discourse::InvalidParameters, "An API with that name already exists: '#{current.title || current.name}'"
if !service_auth_data.nil?
CustomWizard::Api::Authorization.set(service, service_auth_data)
end end
if !service_endpoints.nil? PluginStoreRow.transaction do
service_endpoints.each do |endpoint| CustomWizard::Api.set(api_params[:name], title: api_params[:title])
CustomWizard::Api::Endpoint.set(service, endpoint)
if auth_data.present?
CustomWizard::Api::Authorization.set(api_params[:name], auth_data)
end
if api_params[:endpoints].is_a? String
begin
endpoints = JSON.parse(api_params[:endpoints])
endpoints.each do |endpoint|
CustomWizard::Api::Endpoint.set(api_params[:name], endpoint)
end
rescue => e
puts e
end
end end
end end
render json: success_json.merge( render json: success_json.merge(
api: CustomWizard::ApiSerializer.new(params[:service], root: false) api: CustomWizard::ApiSerializer.new(
CustomWizard::Api.new(api_params[:name]),
root: false
)
) )
end end
def remove
PluginStoreRow.transaction do
CustomWizard::Api.remove(api_params[:name])
CustomWizard::Api::Authorization.remove(api_params[:name])
CustomWizard::Api::Endpoint.remove(api_params[:name])
end
render json: success_json
end
def redirect def redirect
params.require(:service) params.require(:name)
params.require(:code) params.require(:code)
CustomWizard::Api::Authorization.set(params[:service], code: params[:code]) CustomWizard::Api::Authorization.set(params[:name], code: params[:code])
CustomWizard::Api::Authorization.get_token(params[:name])
CustomWizard::Api::Authorization.get_token(params[:service]) return redirect_to path('/admin/wizards/apis/' + params[:name])
return redirect_to path('/admin/wizards/apis/' + params[:service])
end end
private private
def auth_data def api_params
@auth_data ||= params.permit( params.require(:name)
data = params.permit(
:name,
:title,
:auth_type, :auth_type,
:auth_url, :auth_url,
:token_url, :token_url,
@ -67,11 +87,30 @@ class CustomWizard::ApiController < ::ApplicationController
:client_secret, :client_secret,
:username, :username,
:password, :password,
:auth_params :auth_params,
:endpoints,
:new
).to_h ).to_h
data[:name] = data[:name].underscore
@api_params ||= data
end end
def endpoint_data def auth_data
@endpoint_data ||= JSON.parse(params.permit(endpoints: [:id, :type, :url])) auth_data = api_params.slice(
:auth_type,
:auth_url,
:token_url,
:client_id,
:client_secret,
:username,
:password,
:params
)
auth_data[:params] = JSON.parse(auth_data[:params]) if auth_data[:params].present?
@auth_data ||= auth_data
end end
end end

Datei anzeigen

@ -1,7 +1,7 @@
module Jobs module Jobs
class RefreshApiAccessToken < Jobs::Base class RefreshApiAccessToken < Jobs::Base
def execute(args) def execute(args)
CustomWizard::Api::Authorization.refresh_token(args[:service]) CustomWizard::Api::Authorization.refresh_token(args[:name])
end end
end end
end end

Datei anzeigen

@ -1,16 +1,32 @@
class CustomWizard::Api class CustomWizard::Api
include ActiveModel::SerializerSupport include ActiveModel::SerializerSupport
attr_accessor :service attr_accessor :name,
:title
def initialize(service) def initialize(name, data={})
@service = service @name = name
@title = data['title']
end
def self.set(name, data)
PluginStore.set("custom_wizard_api_#{name}", "metadata", data)
end
def self.get(name)
if data = PluginStore.get("custom_wizard_api_#{name}", "metadata")
self.new(name, data)
end
end
def self.remove(name)
PluginStore.remove("custom_wizard_api_#{name}", "metadata")
end end
def self.list def self.list
PluginStoreRow.where("plugin_name LIKE 'custom_wizard_api_%' AND key = 'authorization'") PluginStoreRow.where("plugin_name LIKE 'custom_wizard_api_%' AND key = 'metadata'")
.map do |record| .map do |record|
self.new(record['plugin_name'].split('_').last) self.new(record['plugin_name'].sub("custom_wizard_api_", ""), ::JSON.parse(record['value']))
end end
end end
end end

Datei anzeigen

@ -4,7 +4,7 @@ class CustomWizard::Api::Authorization
include ActiveModel::SerializerSupport include ActiveModel::SerializerSupport
attr_accessor :authorized, attr_accessor :authorized,
:service, :name,
:auth_type, :auth_type,
:auth_url, :auth_url,
:token_url, :token_url,
@ -19,12 +19,15 @@ class CustomWizard::Api::Authorization
:username, :username,
:password :password
def initialize(service, params) def initialize(name, data, opts = {})
@service = service unless opts[:data_only]
data = params.is_a?(String) ? ::JSON.parse(params) : params @name = name
end
data.each do |k, v| if data = data.is_a?(String) ? ::JSON.parse(data) : data
self.send "#{k}=", v if self.respond_to?(k) data.each do |k, v|
self.send "#{k}=", v if self.respond_to?(k)
end
end end
end end
@ -32,52 +35,56 @@ 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(service, data) def self.set(name, data = {})
model = self.get(service) || {} record = self.get(name, data_only: true)
data.each do |k, v| data.each do |k, v|
model.send "#{k}=", v if model.respond_to?(k) record.send "#{k}=", v if record.respond_to?(k)
end end
PluginStore.set("custom_wizard_api_#{service}", 'authorization', model.as_json) PluginStore.set("custom_wizard_api_#{name}", 'authorization', record.as_json)
self.get(service) self.get(name)
end end
def self.get(service) def self.get(name, opts = {})
data = PluginStore.get("custom_wizard_api_#{service}", 'authorization') data = PluginStore.get("custom_wizard_api_#{name}", 'authorization')
self.new(service, data) self.new(name, data, opts)
end end
def self.get_header_authorization_string(service) def self.remove(name)
protocol = authentication_protocol(service) PluginStore.remove("custom_wizard_api_#{name}", "authorization")
raise Discourse::InvalidParameters.new(:service) unless protocol.present? end
def self.get_header_authorization_string(name)
protocol = authentication_protocol(name)
raise Discourse::InvalidParameters.new(:name) unless protocol.present?
raise Discourse::InvalidParameters.new(:protocol) unless [BASIC_AUTH, OAUTH2_AUTH].include? protocol raise Discourse::InvalidParameters.new(:protocol) unless [BASIC_AUTH, OAUTH2_AUTH].include? protocol
if protocol = BASIC_AUTH if protocol = BASIC_AUTH
username = username(service) username = username(name)
raise Discourse::InvalidParameters.new(:username) unless username.present? raise Discourse::InvalidParameters.new(:username) unless username.present?
password = password(service) password = password(name)
raise Discourse::InvalidParameters.new(:password) unless password.present? raise Discourse::InvalidParameters.new(:password) unless password.present?
authorization_string = (username + ":" + password).chomp authorization_string = (username + ":" + password).chomp
"Basic #{Base64.strict_encode64(authorization_string)}" "Basic #{Base64.strict_encode64(authorization_string)}"
else else
# must be OAUTH2 # must be OAUTH2
access_token = access_token(service) access_token = access_token(name)
raise Discourse::InvalidParameters.new(access_token) unless access_token.present? raise Discourse::InvalidParameters.new(access_token) unless access_token.present?
"Bearer #{access_token}" "Bearer #{access_token}"
end end
end end
def self.get_token(service) def self.get_token(name)
authorization = CustomWizard::Api::Authorization.get(service) authorization = CustomWizard::Api::Authorization.get(name)
body = { body = {
client_id: authorization.client_id, client_id: authorization.client_id,
client_secret: authorization.client_secret, client_secret: authorization.client_secret,
code: authorization.code, code: authorization.code,
grant_type: 'authorization_code', grant_type: 'authorization_code',
redirect_uri: Discourse.base_url + "/admin/wizards/apis/#{service}/redirect" redirect_uri: Discourse.base_url + "/admin/wizards/apis/#{name}/redirect"
} }
result = Excon.post( result = Excon.post(
@ -88,11 +95,11 @@ class CustomWizard::Api::Authorization
:body => URI.encode_www_form(body) :body => URI.encode_www_form(body)
) )
self.handle_token_result(service, result) self.handle_token_result(name, result)
end end
def self.refresh_token(service) def self.refresh_token(name)
authorization = CustomWizard::Api::Authorization.get(service) authorization = CustomWizard::Api::Authorization.get(name)
body = { body = {
grant_type: 'refresh_token', grant_type: 'refresh_token',
@ -110,10 +117,10 @@ class CustomWizard::Api::Authorization
:body => URI.encode_www_form(body) :body => URI.encode_www_form(body)
) )
self.handle_token_result(service, result) self.handle_token_result(name, result)
end end
def self.handle_token_result(service, result) def self.handle_token_result(name, result)
data = JSON.parse(result.body) data = JSON.parse(result.body)
return false if (data['error']) return false if (data['error'])
@ -124,12 +131,12 @@ class CustomWizard::Api::Authorization
refresh_at = expires_at.to_time - 2.hours refresh_at = expires_at.to_time - 2.hours
opts = { opts = {
service: service name: name
} }
Jobs.enqueue_at(refresh_at, :refresh_api_access_token, opts) Jobs.enqueue_at(refresh_at, :refresh_api_access_token, opts)
CustomWizard::Api::Authorization.set(service, CustomWizard::Api::Authorization.set(name,
access_token: access_token, access_token: access_token,
refresh_token: refresh_token, refresh_token: refresh_token,
token_expires_at: expires_at, token_expires_at: expires_at,

Datei anzeigen

@ -5,42 +5,46 @@ class CustomWizard::Api::Endpoint
:method, :method,
:url :url
def initialize(service, params) def initialize(name, params)
@service = service @name = name
data = params.is_a?(String) ? ::JSON.parse(params) : params if data = params.is_a?(String) ? ::JSON.parse(params) : params
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
def self.set(service, data) def self.set(name, data)
model = data[:endpoint_id] ? self.get(service, data[:endpoint_id]) : {} model = data[:endpoint_id] ? self.get(name, data[:endpoint_id]) : {}
endpoint_id = model[:endpoint_id] || SecureRandom.hex(8) 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) model.send "#{k}=", v if model.respond_to?(k)
end end
PluginStore.set("custom_wizard_api_#{service}", "endpoint_#{endpoint_id}", model.as_json) PluginStore.set("custom_wizard_api_#{name}", "endpoint_#{endpoint_id}", model.as_json)
self.get(service) self.get(name)
end end
def self.get(service, endpoint_id) def self.get(name, endpoint_id)
return nil if !endpoint_id return nil if !endpoint_id
data = PluginStore.get("custom_wizard_api_#{service}", "endpoint_#{endpoint_id}") data = PluginStore.get("custom_wizard_api_#{name}", "endpoint_#{endpoint_id}")
data[:id] = endpoint_id data[:id] = endpoint_id
self.new(service, data) self.new(name, data)
end
def self.remove(name)
PluginStoreRow.where("plugin_name = 'custom_wizard_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|
service = record['plugin_name'].split('_').last 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(service, data) self.new(name, data)
end end
end end
end end

Datei anzeigen

@ -69,9 +69,10 @@ after_initialize do
get 'admin/wizards/submissions/:wizard_id' => 'admin#submissions' get 'admin/wizards/submissions/:wizard_id' => 'admin#submissions'
get 'admin/wizards/apis' => 'api#list' get 'admin/wizards/apis' => 'api#list'
get 'admin/wizards/apis/new' => 'api#index' get 'admin/wizards/apis/new' => 'api#index'
get 'admin/wizards/apis/:service' => 'api#find' get 'admin/wizards/apis/:name' => 'api#find'
put 'admin/wizards/apis/:service' => 'api#save' put 'admin/wizards/apis/:name' => 'api#save'
get 'admin/wizards/apis/:service/redirect' => 'api#redirect' delete 'admin/wizards/apis/:name' => 'api#remove'
get 'admin/wizards/apis/:name/redirect' => 'api#redirect'
end end
end end

Datei anzeigen

@ -1,19 +1,24 @@
class CustomWizard::ApiSerializer < ApplicationSerializer class CustomWizard::ApiSerializer < ApplicationSerializer
attributes :service, attributes :name,
:title,
:authorization, :authorization,
:endpoints :endpoints
def authorization def authorization
CustomWizard::Api::AuthorizationSerializer.new( if authorization = CustomWizard::Api::Authorization.get(object.name)
CustomWizard::Api::Authorization.get(object.service), CustomWizard::Api::AuthorizationSerializer.new(
root: false authorization,
) root: false
)
end
end end
def endpoints def endpoints
ActiveModel::ArraySerializer.new( if endpoints = CustomWizard::Api::Endpoint.list
CustomWizard::Api::Endpoint.list, ActiveModel::ArraySerializer.new(
each_serializer: CustomWizard::Api::EndpointSerializer endpoints,
) each_serializer: CustomWizard::Api::EndpointSerializer
)
end
end end
end end

Datei anzeigen

@ -1,3 +1,3 @@
class CustomWizard::BasicApiSerializer < ApplicationSerializer class CustomWizard::BasicApiSerializer < ApplicationSerializer
attributes :service attributes :name, :title
end end