From 6a733396f924c3933e51fd9530e2b26b13e30181 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 28 Aug 2015 02:53:14 +0200 Subject: [PATCH] Rewrite of channel api. --- .../app/controllers/_channel/email.js.coffee | 802 ++++++++++++------ .../app/controllers/getting_started.js.coffee | 155 +++- .../javascripts/app/models/channel.js.coffee | 30 +- .../app/models/email_address.js.coffee | 13 +- .../channel/email_account_overview.jst.eco | 99 +++ .../channel/email_account_wizard.jst.eco | 92 ++ .../channel/email_notification_wizard.jst.eco | 39 + .../app/views/channel/email_outbound.jst.eco | 5 - .../app/views/getting_started/channel.jst.eco | 2 +- .../app/views/getting_started/email.jst.eco | 4 +- .../email_notification.jst.eco | 30 + app/controllers/channels_controller.rb | 290 ++++++- app/controllers/getting_started_controller.rb | 113 --- app/models/channel.rb | 119 ++- app/models/channel/driver.rb | 4 + app/models/channel/{ => driver}/facebook.rb | 4 +- app/models/channel/{ => driver}/imap.rb | 18 +- app/models/channel/{ => driver}/mail_stdin.rb | 2 +- app/models/channel/{ => driver}/pop3.rb | 12 +- app/models/channel/{ => driver}/sendmail.rb | 4 +- app/models/channel/{ => driver}/smtp.rb | 14 +- app/models/channel/{ => driver}/twitter.rb | 6 +- app/models/channel/email_send.rb | 28 - app/models/email_address.rb | 1 + .../communicate_email/background_job.rb | 10 +- .../ticket/article/communicate_facebook.rb | 4 +- .../ticket/article/communicate_twitter.rb | 4 +- config/routes/channel.rb | 8 + config/routes/getting_started.rb | 12 +- db/migrate/20150824000002_update_channel.rb | 57 ++ db/seeds.rb | 25 +- lib/email_helper/probe.rb | 109 ++- lib/email_helper/verify.rb | 11 +- lib/notification_factory.rb | 4 +- public/assets/tests/table.js | 13 + test/browser/aaa_getting_started_test.rb | 30 +- test/integration/facebook_test.rb | 4 +- test/integration/twitter_test.rb | 6 +- 38 files changed, 1570 insertions(+), 613 deletions(-) create mode 100644 app/assets/javascripts/app/views/channel/email_account_overview.jst.eco create mode 100644 app/assets/javascripts/app/views/channel/email_account_wizard.jst.eco create mode 100644 app/assets/javascripts/app/views/channel/email_notification_wizard.jst.eco delete mode 100644 app/assets/javascripts/app/views/channel/email_outbound.jst.eco create mode 100644 app/assets/javascripts/app/views/getting_started/email_notification.jst.eco create mode 100644 app/models/channel/driver.rb rename app/models/channel/{ => driver}/facebook.rb (93%) rename app/models/channel/{ => driver}/imap.rb (75%) rename app/models/channel/{ => driver}/mail_stdin.rb (77%) rename app/models/channel/{ => driver}/pop3.rb (77%) rename app/models/channel/{ => driver}/sendmail.rb (77%) rename app/models/channel/{ => driver}/smtp.rb (55%) rename app/models/channel/{ => driver}/twitter.rb (94%) delete mode 100644 app/models/channel/email_send.rb create mode 100644 db/migrate/20150824000002_update_channel.rb diff --git a/app/assets/javascripts/app/controllers/_channel/email.js.coffee b/app/assets/javascripts/app/controllers/_channel/email.js.coffee index 64245b96b..173e6ed49 100644 --- a/app/assets/javascripts/app/controllers/_channel/email.js.coffee +++ b/app/assets/javascripts/app/controllers/_channel/email.js.coffee @@ -7,19 +7,9 @@ class App.ChannelEmail extends App.ControllerTabs @tabs = [ { - name: 'Inbound', - target: 'c-inbound', - controller: App.ChannelEmailInbound, - }, - { - name: 'Outbound', - target: 'c-outbound', - controller: App.ChannelEmailOutbound, - }, - { - name: 'Adresses', - target: 'c-address', - controller: App.ChannelEmailAddress, + name: 'Email Accounts', + target: 'c-channel', + controller: App.ChannelEmailAccountOverview, }, { name: 'Signatures', @@ -132,100 +122,6 @@ class App.ChannelEmailFilterEdit extends App.ControllerModal @hide() ) - -class App.ChannelEmailAddress extends App.Controller - events: - 'click [data-type=new]': 'new' - - constructor: -> - super - - App.EmailAddress.subscribe( @render, initFetch: true ) - - render: => - data = App.EmailAddress.search( sortBy: 'realname' ) - - template = $( '
' + App.i18n.translateContent('New') + '
' ) - - new App.ControllerTable( - el: template.find('.overview') - model: App.EmailAddress - objects: data - bindRow: - events: - 'click': @edit - ) - - @html template - - new: (e) => - e.preventDefault() - new App.ChannelEmailAddressEdit( - container: @el.closest('.content') - ) - - edit: (id, e) => - e.preventDefault() - item = App.EmailAddress.find(id) - new App.ChannelEmailAddressEdit( - object: item - container: @el.closest('.content') - ) - -class App.ChannelEmailAddressEdit extends App.ControllerModal - constructor: -> - super - - @head = 'Email-Address' - @button = true - @close = true - @cancel = true - - if @object - @form = new App.ControllerForm( - model: App.EmailAddress - params: @object - autofocus: true - ) - else - @form = new App.ControllerForm( - model: App.EmailAddress, - autofocus: true, - ) - - @content = @form.form - - @show() - - onSubmit: (e) => - e.preventDefault() - - # get params - params = @formParam(e.target) - - object = @object || new App.EmailAddress - object.load(params) - - # validate form - errors = @form.validate( params ) - - # show errors in form - if errors - @log 'error', errors - @formValidate( form: e.target, errors: errors ) - return false - - # disable form - @formDisable(e) - - # save object - object.save( - done: => - @hide() - fail: => - @hide() - ) - class App.ChannelEmailSignature extends App.Controller events: 'click [data-type=new]': 'new' @@ -317,193 +213,575 @@ class App.ChannelEmailSignatureEdit extends App.ControllerModal @hide() ) -class App.ChannelEmailInbound extends App.Controller +class App.ChannelEmailAccountOverview extends App.Controller events: - 'click [data-type=new]': 'new' + 'click [data-type="new"]': 'wizard' + 'click [data-type="delete"]': 'delete' + 'click [data-type="edit-inbound"]': 'edit_inbound' + 'click [data-type="edit-outbound"]': 'edit_outbound' + 'click [data-type="email-address-new"]': 'email_address_new' + 'click [data-type="email-address-edit"]': 'email_address_edit' + 'click [data-type="edit-notification-outbound"]': 'edit_notification_outbound' constructor: -> super - App.Channel.subscribe( @render, initFetch: true ) + @interval(@load, 20000) - render: => - channels = App.Channel.search( filter: { area: 'Email::Inbound' } ) + load: => + @ajax( + id: 'email_index' + type: 'GET' + url: @apiPath + '/channels/email_index' + processData: true + success: (data, status, xhr) => - template = $( '
' + App.i18n.translateContent('New') + '
' ) + # load assets + App.Collection.loadAssets( data.assets ) - new App.ControllerTable( - el: template.find('.overview') - model: App.Channel - objects: channels - bindRow: - events: - 'click': @edit + @render() ) - @html template - - new: (e) => - e.preventDefault() - new App.ChannelEmailInboundEdit( - container: @el.closest('.content') - ) - - edit: (id, e) => - e.preventDefault() - item = App.Channel.find(id) - new App.ChannelEmailInboundEdit( - object: item - container: @el.closest('.content') - ) - - -class App.ChannelEmailInboundEdit extends App.ControllerModal - constructor: -> - super - - @head = 'Email Channel' - @button = true - @close = true - @cancel = true - - if @object - @form = new App.ControllerForm( - model: App.Channel - params: @object - autofocus: true - ) - else - @form = new App.ControllerForm( - model: App.Channel - autofocus: true - ) - - @content = @form.form - - @show() - - onSubmit: (e) => - e.preventDefault() - - # get params - params = @formParam(e.target) - params['area'] = 'Email::Inbound' - - object = @object || new App.Channel - object.load(params) - - # validate form - errors = @form.validate( params ) - - # show errors in form - if errors - @log 'error', errors - @formValidate( form: e.target, errors: errors ) - return false - - # disable form - @formDisable(e) - - # save object - object.save( - done: => - @hide() - fail: => - @hide() - ) - -class App.ChannelEmailOutbound extends App.Controller - events: - 'change [name="adapter"]': 'toggle' - 'submit #mail_adapter': 'update' - - constructor: -> - super - - App.Channel.subscribe( @render, initFetch: true ) render: => - @html App.view('channel/email_outbound')() - - # get current Email::Outbound channel - channels = App.Channel.all() - adapters = {} - adapter_used = undefined - channel_used = undefined + # get channels + channels = App.Channel.search( filter: { area: 'Email::Account' } ) for channel in channels - if channel.area is 'Email::Outbound' + email_addresses = App.EmailAddress.search( filter: { channel_id: channel.id } ) + channel.email_addresses = email_addresses - adapters[channel.adapter] = channel.adapter - if @adapter_used - if @adapter_used is channel.adapter - adapter_used = channel.adapter - channel_used = channel - else if channel.active is true - adapter_used = channel.adapter - channel_used = channel + # get all unlinked email addresses + email_addresses_all = App.EmailAddress.all() + email_addresses_not_used = [] + for email_address in email_addresses_all + if !email_address.channel_id || !App.Channel.exists(email_address.channel_id) + email_addresses_not_used.push email_address - configure_attributes = [ - { name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: adapters , default: adapter_used }, + # get channels + channel = App.Channel.search( filter: { area: 'Email::Notification', active: true } )[0] + + @html App.view('channel/email_account_overview')( + channels: channels + email_addresses_not_used: email_addresses_not_used + channel: channel + ) + + wizard: (e) => + e.preventDefault() + new App.ChannelEmailAccountWizard( + container: @el.closest('.content') + callback: @load + ) + + edit_inbound: (e) => + e.preventDefault() + id = $(e.target).closest('tr').data('id') + channel = App.Channel.find(id) + slide = 'js-inbound' + new App.ChannelEmailAccountWizard( + container: @el.closest('.content') + slide: slide + channel: channel + callback: @load + ) + + edit_outbound: (e) => + e.preventDefault() + id = $(e.target).closest('tr').data('id') + channel = App.Channel.find(id) + slide = 'js-outbound' + new App.ChannelEmailAccountWizard( + container: @el.closest('.content') + slide: slide + channel: channel + callback: @load + ) + + delete: (e) => + e.preventDefault() + id = $(e.target).closest('tr').data('id') + item = App.Channel.find(id) + new App.ControllerGenericDestroyConfirm( + item: item + container: @el.closest('.content') + callback: @load + ) + + email_address_new: (e) => + e.preventDefault() + channel_id = $(e.target).closest('tr').data('id') + new App.ControllerGenericNew( + pageData: + object: 'Email Address' + genericObject: 'EmailAddress' + container: @el.closest('.content') + item: + channel_id: channel_id + callback: @load + ) + + email_address_edit: (e) => + e.preventDefault() + id = $(e.target).closest('li').data('id') + new App.ControllerGenericEdit( + pageData: + object: 'Email Address' + genericObject: 'EmailAddress' + container: @el.closest('.content') + id: id + callback: @load + ) + + edit_notification_outbound: (e) => + e.preventDefault() + id = $(e.target).closest('tr').data('id') + channel = App.Channel.find(id) + slide = 'js-outbound' + new App.ChannelEmailNotificationWizard( + container: @el.closest('.content') + channel: channel + callback: @load + ) + +class App.ChannelEmailAccountWizard extends App.Controller + elements: + '.modal-body': 'body' + + className: 'modal fade' + + events: + 'submit .js-intro': 'probeBasedOnIntro' + 'submit .js-inbound': 'probeInbound' + 'change .js-outbound [name=adapter]': 'toggleOutboundAdapter' + 'submit .js-outbound': 'probleOutbound' + 'click .js-back': 'goToSlide' + + constructor: -> + super + + # store account settings + @account = + inbound: + adapter: undefined + options: undefined + outbound: + adapter: undefined + options: undefined + meta: {} + + if @channel + @account = + inbound: @channel.options.inbound + outbound: @channel.options.outbound + meta: {} + + if @container + @el.addClass('modal--local') + + @render() + + @el.modal + keyboard: true + show: true + backdrop: true + container: @container + .on + 'show.bs.modal': @onShow + 'shown.bs.modal': @onComplete + 'hidden.bs.modal': => + if @callback + @callback() + $('.modal').remove() + + if @slide + @showSlide(@slide) + + render: => + @html App.view('channel/email_account_wizard')() + @showSlide('js-intro') + + # outbound + adapters = + sendmail: 'Local MTA (Sendmail/Postfix/Exim/...) - use server setup' + smtp: 'SMTP - configure your own outgoing SMTP settings' + configureAttributesOutbound = [ + { name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: adapters }, ] new App.ControllerForm( - el: @el.find('#form-email-adapter'), - model: { configure_attributes: configure_attributes, className: '' }, - autofocus: true, + el: @$('.base-outbound-type') + model: + configure_attributes: configureAttributesOutbound + className: '' + params: + adapter: @account.outbound.adapter || 'sendmail' + ) + @toggleOutboundAdapter() + + # inbound + configureAttributesInbound = [ + { name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: { imap: 'IMAP', pop3: 'POP3' } }, + { name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false }, + { name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false }, + { name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false, single: true }, + ] + new App.ControllerForm( + el: @$('.base-inbound-settings'), + model: + configure_attributes: configureAttributesInbound + className: '' + params: @account.inbound ) -# if adapter_used is 'Sendmail' -# # some form + toggleOutboundAdapter: => - if adapter_used is 'SMTP' - configure_attributes = [ - { name: 'host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, default: (channel_used['options']&&channel_used['options']['host']) }, - { name: 'user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, default: (channel_used['options']&&channel_used['options']['user']) }, - { name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, default: (channel_used['options']&&channel_used['options']['password']) }, - { name: 'ssl', display: 'SSL', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' } , translate: true, default: (channel_used['options']&&channel_used['options']['ssl']) }, - { name: 'port', display: 'Port', tag: 'input', type: 'text', limit: 5, null: false, class: 'span1', autocapitalize: false, default: ((channel_used['options']&&channel_used['options']['port']) || 25) }, + # fill user / password based on intro info + channel_used = { options: {} } + if @account['meta'] + channel_used['options']['user'] = @account['meta']['email'] + channel_used['options']['password'] = @account['meta']['password'] + + # show used backend + @el.find('.base-outbound-settings').html('') + adapter = @$('.js-outbound [name=adapter]').val() + if adapter is 'smtp' + configureAttributesOutbound = [ + { name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true }, + { name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false }, + { name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, single: true }, ] @form = new App.ControllerForm( - el: @el.find('#form-email-adapter-settings'), - model: { configure_attributes: configure_attributes, className: '' }, - autofocus: true, + el: @$('.base-outbound-settings') + model: + configure_attributes: configureAttributesOutbound + className: '' + params: @account.outbound ) - toggle: (e) => + probeBasedOnIntro: (e) => + e.preventDefault() + params = @formParam(e.target) + + # remember account settings + @account.meta = params + + # let backend know about the channel + if @channel + params.channel_id = @channel.id + + @disable(e) + @$('.js-probe .js-email').text( params.email ) + @showSlide('js-probe') + + @ajax( + id: 'email_probe' + type: 'POST' + url: @apiPath + '/channels/email_probe' + data: JSON.stringify( params ) + processData: true + success: (data, status, xhr) => + if data.result is 'ok' + if data.setting + for key, value of data.setting + @account[key] = value + @verify(@account) + else if data.result is 'duplicate' + @showSlide('js-intro') + @showAlert('js-intro', 'Account already exists!' ) + else + @showSlide('js-inbound') + @showAlert('js-inbound', 'Unable to detect your server settings. Manual configuration needed.' ) + @$('.js-inbound [name="options::user"]').val( @account['meta']['email'] ) + @$('.js-inbound [name="options::password"]').val( @account['meta']['password'] ) + + @enable(e) + fail: => + @enable(e) + @showSlide('js-intro') + ) + + probeInbound: (e) => + e.preventDefault() # get params params = @formParam(e.target) - # render page with new selected adapter - if @adapter_used isnt params['adapter'] + # let backend know about the channel + params.channel_id = @channel.id - # set selected adapter - @adapter_used = params['adapter'] + @disable(e) - @render() + @showSlide('js-test') - update: (e) => + @ajax( + id: 'email_inbound' + type: 'POST' + url: @apiPath + '/channels/email_inbound' + data: JSON.stringify( params ) + processData: true + success: (data, status, xhr) => + if data.result is 'ok' + + # remember account settings + @account.inbound = params + + @showSlide('js-outbound') + if !@channel + @$('.js-outbound [name="options::user"]').val( @account['meta']['email'] ) + @$('.js-outbound [name="options::password"]').val( @account['meta']['password'] ) + + else + @showSlide('js-inbound') + @showAlert('js-inbound', data.message_human || data.message ) + @enable(e) + fail: => + @showSlide('js-inbound') + @showAlert('js-inbound', data.message_human || data.message ) + @enable(e) + ) + + probleOutbound: (e) => e.preventDefault() - params = @formParam(e.target) -# errors = @form.validate( params ) + # get params + params = @formParam(e.target) + params['email'] = @account['meta']['email'] - # update Email::Outbound adapter - channels = App.Channel.all() - for channel in channels - if channel.area is 'Email::Outbound' && channel.adapter is params['adapter'] - channel.updateAttributes( - options: { - host: params['host'], - user: params['user'], - password: params['password'], - ssl: params['ssl'], - port: params['port'], - }, - active: true, - ) + if !params['email'] && @channel + email_addresses = App.EmailAddress.search( filter: { channel_id: @channel.id } ) + if email_addresses && email_addresses[0] + params['email'] = email_addresses[0].email - # set all other Email::Outbound adapters to inactive - channels = App.Channel.all() - for channel in channels - if channel.area is 'Email::Outbound' && channel.adapter isnt params['adapter'] - channel.updateAttributes( active: false ) + # let backend know about the channel + params.channel_id = @channel.id + @disable(e) + + @showSlide('js-test') + + @ajax( + id: 'email_outbound' + type: 'POST' + url: @apiPath + '/channels/email_outbound' + data: JSON.stringify( params ) + processData: true + success: (data, status, xhr) => + if data.result is 'ok' + + # remember account settings + @account.outbound = params + + @verify(@account) + else + @showSlide('js-outbound') + @showAlert('js-outbound', data.message_human || data.message ) + @enable(e) + fail: => + @showSlide('js-outbound') + @showAlert('js-outbound', data.message_human || data.message ) + @enable(e) + ) + + verify: (account, count = 0) => + @showSlide('js-verify') + + # let backend know about the channel + if @channel + account.channel_id = @channel.id + + if !account.email && @channel + email_addresses = App.EmailAddress.search( filter: { channel_id: @channel.id } ) + if email_addresses && email_addresses[0] + account.email = email_addresses[0].email + + @ajax( + id: 'email_verify' + type: 'POST' + url: @apiPath + '/channels/email_verify' + data: JSON.stringify( account ) + processData: true + success: (data, status, xhr) => + if data.result is 'ok' + @el.remove() + else + if count is 2 + @showAlert('js-verify', data.message_human || data.message ) + @delay( + => + @showSlide('js-intro') + @showAlert('js-intro', 'Unable to verify sending and receiving. Please check your settings.' ) + + 2300 + ) + else + if data.subject && @account + @account.subject = data.subject + @verify( @account, count + 1 ) + fail: => + @showSlide('js-intro') + @showAlert('js-intro', 'Unable to verify sending and receiving. Please check your settings.' ) + ) + + goToSlide: (e) => + e.preventDefault() + slide = $(e.target).data('slide') + @showSlide(slide) + + showSlide: (name) => + @hideAlert(name) + @$('.setup.wizard').addClass('hide') + @$(".setup.wizard.#{name}").removeClass('hide') + @$(".setup.wizard.#{name} input, .setup.wizard.#{name} select").first().focus() + + showAlert: (screen, message) => + @$(".#{screen}").find('.alert').removeClass('hide').text( App.i18n.translateInline( message ) ) + + hideAlert: (screen) => + @$(".#{screen}").find('.alert').addClass('hide') + + disable: (e) => + @formDisable(e) + @$('.wizard-controls .btn').attr('disabled', true) + + enable: (e) => + @formEnable(e) + @$('.wizard-controls .btn').attr('disabled', false) + +class App.ChannelEmailNotificationWizard extends App.Controller + elements: + '.modal-body': 'body' + + className: 'modal fade' + + events: + 'change .js-outbound [name=adapter]': 'toggleOutboundAdapter' + 'submit .js-outbound': 'probleOutbound' + + constructor: -> + super + + # store account settings + @account = + inbound: + adapter: undefined + options: undefined + outbound: + adapter: undefined + options: undefined + meta: {} + + if @channel + @account = + inbound: @channel.options.inbound + outbound: @channel.options.outbound + + if @container + @el.addClass('modal--local') + + @render() + + @el.modal + keyboard: true + show: true + backdrop: true + container: @container + .on + 'show.bs.modal': @onShow + 'shown.bs.modal': @onComplete + 'hidden.bs.modal': => + if @callback + @callback() + $('.modal').remove() + + if @slide + @showSlide(@slide) + + render: => + @html App.view('channel/email_notification_wizard')() + @showSlide('js-outbound') + + # outbound + adapters = + sendmail: 'Local MTA (Sendmail/Postfix/Exim/...) - use server setup' + smtp: 'SMTP - configure your own outgoing SMTP settings' + configureAttributesOutbound = [ + { name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: adapters }, + ] + new App.ControllerForm( + el: @$('.base-outbound-type') + model: + configure_attributes: configureAttributesOutbound + className: '' + params: + adapter: @account.outbound.adapter || 'sendmail' + ) + @toggleOutboundAdapter() + + toggleOutboundAdapter: => + + # show used backend + @el.find('.base-outbound-settings').html('') + adapter = @$('.js-outbound [name=adapter]').val() + if adapter is 'smtp' + configureAttributesOutbound = [ + { name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true }, + { name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false }, + { name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, single: true }, + ] + @form = new App.ControllerForm( + el: @$('.base-outbound-settings') + model: + configure_attributes: configureAttributesOutbound + className: '' + params: @account.outbound + ) + + probleOutbound: (e) => + e.preventDefault() + + # get params + params = @formParam(e.target) + + # let backend know about the channel + params.channel_id = @channel.id + + @disable(e) + + @showSlide('js-test') + + @ajax( + id: 'email_outbound' + type: 'POST' + url: @apiPath + '/channels/email_notification' + data: JSON.stringify( params ) + processData: true + success: (data, status, xhr) => + if data.result is 'ok' + @el.remove() + else + @showSlide('js-outbound') + @showAlert('js-outbound', data.message_human || data.message ) + @enable(e) + fail: => + @showSlide('js-outbound') + @showAlert('js-outbound', data.message_human || data.message ) + @enable(e) + ) + + showSlide: (name) => + @hideAlert(name) + @$('.setup.wizard').addClass('hide') + @$(".setup.wizard.#{name}").removeClass('hide') + @$(".setup.wizard.#{name} input, .setup.wizard.#{name} select").first().focus() + + showAlert: (screen, message) => + @$(".#{screen}").find('.alert').removeClass('hide').text( App.i18n.translateInline( message ) ) + + hideAlert: (screen) => + @$(".#{screen}").find('.alert').addClass('hide') + + disable: (e) => + @formDisable(e) + @$('.wizard-controls .btn').attr('disabled', true) + + enable: (e) => + @formEnable(e) + @$('.wizard-controls .btn').attr('disabled', false) \ No newline at end of file diff --git a/app/assets/javascripts/app/controllers/getting_started.js.coffee b/app/assets/javascripts/app/controllers/getting_started.js.coffee index 87b375439..becf4d996 100644 --- a/app/assets/javascripts/app/controllers/getting_started.js.coffee +++ b/app/assets/javascripts/app/controllers/getting_started.js.coffee @@ -364,7 +364,7 @@ class Base extends App.ControllerContent if App.Config.get('system_online_service') @navigate 'getting_started/channel/email_pre_configured' else - @navigate 'getting_started/channel' + @navigate 'getting_started/email_notification' else for key, value of data.messages @showAlert( key, value ) @@ -394,6 +394,151 @@ class Base extends App.ControllerContent App.Config.set( 'getting_started/base', Base, 'Routes' ) +class EmailNotification extends App.ControllerContent + className: 'getstarted fit' + events: + 'change .js-outbound [name=adapter]': 'toggleOutboundAdapter' + 'submit .js-outbound': 'submit' + + constructor: -> + super + + # redirect if we are not admin + if !@authenticate(true) + @navigate '#' + return + + # set title + @title 'Email Notifications' + + @adapters = [ + { + name: 'Email' + class: 'email' + link: '#getting_started/channel/email' + }, + ] + + @fetch() + + release: => + @el.removeClass('fit getstarted') + + fetch: -> + + # get data + @ajax( + id: 'getting_started', + type: 'GET', + url: @apiPath + '/getting_started', + processData: true, + success: (data, status, xhr) => + + # check if import is active + if data.import_mode == true + @navigate '#import/' + data.import_backend + return + + # render page + @render() + ) + + render: -> + @html App.view('getting_started/email_notification')() + adapters = + sendmail: 'Local MTA (Sendmail/Postfix/Exim/...) - use server setup' + smtp: 'SMTP - configure your own outgoing SMTP settings' + adapter_used = 'sendmail' + configureAttributesOutbound = [ + { name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: adapters , default: adapter_used }, + ] + new App.ControllerForm( + el: @$('.base-outbound-type'), + model: { configure_attributes: configureAttributesOutbound, className: '' }, + ) + @toggleOutboundAdapter() + + toggleOutboundAdapter: => + + # show used backend + channel_used = { options: {} } + adapter = @$('.js-outbound [name=adapter]').val() + if adapter is 'smtp' + configureAttributesOutbound = [ + { name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, autofocus: true, default: (channel_used['options']&&channel_used['options']['host']) }, + { name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, default: (channel_used['options']&&channel_used['options']['user']) }, + { name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, single: true, default: (channel_used['options']&&channel_used['options']['password']) }, + ] + @form = new App.ControllerForm( + el: @$('.base-outbound-settings') + model: { configure_attributes: configureAttributesOutbound, className: '' } + ) + else + @el.find('.base-outbound-settings').html('') + + + submit: (e) => + e.preventDefault() + + # get params + params = @formParam(e.target) + params['email'] = 'me@localhost' + @disable(e) + + @showSlide('js-test') + + @ajax( + id: 'email_notification' + type: 'POST' + url: @apiPath + '/channels/email_notification' + data: JSON.stringify( params ) + processData: true + success: (data, status, xhr) => + if data.result is 'ok' + for key, value of data.settings + App.Config.set( key, value ) + if App.Config.get('system_online_service') + @navigate 'getting_started/channel/email_pre_configured' + else + @navigate 'getting_started/channel' + else + @showSlide('js-outbound') + @showAlert('js-outbound', data.message_human || data.message ) + @enable(e) + + fail: => + @showSlide('js-outbound') + @showAlert('js-outbound', data.message_human || data.message ) + @enable(e) + ) + + goToSlide: (e) => + e.preventDefault() + slide = $(e.target).data('slide') + @showSlide(slide) + + showSlide: (name) => + @hideAlert(name) + @$('.setup.wizard').addClass('hide') + @$(".setup.wizard.#{name}").removeClass('hide') + @$(".setup.wizard.#{name} input, .setup.wizard.#{name} select").first().focus() + + showAlert: (screen, message) => + @$(".#{screen}").find('.alert').removeClass('hide').text( App.i18n.translateInline( message ) ) + + hideAlert: (screen) => + @$(".#{screen}").find('.alert').addClass('hide') + + disable: (e) => + @formDisable(e) + @$('.wizard-controls .btn').attr('disabled', true) + + enable: (e) => + @formEnable(e) + @$('.wizard-controls .btn').attr('disabled', false) + +App.Config.set( 'getting_started/email_notification', EmailNotification, 'Routes' ) + class Channel extends App.ControllerContent className: 'getstarted fit' @@ -610,7 +755,7 @@ class ChannelEmail extends App.ControllerContent @ajax( id: 'email_probe' type: 'POST' - url: @apiPath + '/getting_started/email_probe' + url: @apiPath + '/channels/email_probe' data: JSON.stringify( params ) processData: true success: (data, status, xhr) => @@ -643,7 +788,7 @@ class ChannelEmail extends App.ControllerContent @ajax( id: 'email_inbound' type: 'POST' - url: @apiPath + '/getting_started/email_inbound' + url: @apiPath + '/channels/email_inbound' data: JSON.stringify( params ) processData: true success: (data, status, xhr) => @@ -679,7 +824,7 @@ class ChannelEmail extends App.ControllerContent @ajax( id: 'email_outbound' type: 'POST' - url: @apiPath + '/getting_started/email_outbound' + url: @apiPath + '/channels/email_outbound' data: JSON.stringify( params ) processData: true success: (data, status, xhr) => @@ -705,7 +850,7 @@ class ChannelEmail extends App.ControllerContent @ajax( id: 'email_verify' type: 'POST' - url: @apiPath + '/getting_started/email_verify' + url: @apiPath + '/channels/email_verify' data: JSON.stringify( account ) processData: true success: (data, status, xhr) => diff --git a/app/assets/javascripts/app/models/channel.js.coffee b/app/assets/javascripts/app/models/channel.js.coffee index 334c18409..301f00451 100644 --- a/app/assets/javascripts/app/models/channel.js.coffee +++ b/app/assets/javascripts/app/models/channel.js.coffee @@ -2,18 +2,20 @@ class App.Channel extends App.Model @configure 'Channel', 'adapter', 'area', 'options', 'group_id', 'active', 'updated_at' @extend Spine.Model.Ajax @url: @apiPath + '/channels' - @configure_delete = true - @configure_attributes = [ - { name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: { IMAP: 'IMAP', POP3: 'POP3' } }, - { name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false }, - { name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false }, - { name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false }, - { name: 'options::ssl', display: 'SSL', tag: 'select', multiple: false, null: true, options: { true: 'yes', false: 'no' }, translate: true, default: true}, - { name: 'options::folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false }, - { name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: false, nulloption: true, relation: 'Group' }, - { name: 'active', display: 'Active', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' }, translate: true, default: true }, - ] - @configure_overview = [ - 'adapter', 'options::host', 'options::user', 'group' - ] + displayName: -> + name = '' + if @options + if @options.inbound + name += "#{@options.inbound.options.user}@#{@options.inbound.options.host} (#{@options.inbound.adapter})" + if @options.outbound + if @options.outbound + if name != '' + name += ' / ' + if @options.outbound.options + name += "#{@options.outbound.options.host} (#{@options.outbound.adapter})" + else + name += " (#{@options.outbound.adapter})" + if name == '' + name = '???' + name \ No newline at end of file diff --git a/app/assets/javascripts/app/models/email_address.js.coffee b/app/assets/javascripts/app/models/email_address.js.coffee index 3da7514b5..33691fc13 100644 --- a/app/assets/javascripts/app/models/email_address.js.coffee +++ b/app/assets/javascripts/app/models/email_address.js.coffee @@ -1,11 +1,20 @@ class App.EmailAddress extends App.Model - @configure 'EmailAddress', 'realname', 'email', 'note', 'active', 'updated_at' + @configure 'EmailAddress', 'realname', 'email', 'channel_id', 'note', 'active', 'updated_at' @extend Spine.Model.Ajax @url: @apiPath + '/email_addresses' + @filterChannel: (options, type) => + return options if type isnt 'collection' + _.filter( + options + (channel) -> + return channel if channel && channel.area is 'Email::Account' + ) + @configure_attributes = [ { name: 'realname', display: 'Realname', tag: 'input', type: 'text', limit: 250, null: false }, - { name: 'email', display: 'Email', tag: 'input', type: 'text', limit: 250, null: false }, + { name: 'email', display: 'Email', tag: 'input', type: 'email', limit: 250, null: false }, + { name: 'channel_id', display: 'Channel', tag: 'select', multiple: false, null: false, relation: 'Channel', nulloption: true, filter: @filterChannel }, { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 }, { name: 'active', display: 'Active', tag: 'active', default: true }, diff --git a/app/assets/javascripts/app/views/channel/email_account_overview.jst.eco b/app/assets/javascripts/app/views/channel/email_account_overview.jst.eco new file mode 100644 index 000000000..84ff66c2d --- /dev/null +++ b/app/assets/javascripts/app/views/channel/email_account_overview.jst.eco @@ -0,0 +1,99 @@ +

<%- @T('Email Accounts') %>

+ +<% if !_.isEmpty(@email_addresses_not_used): %> +

<%- @T('Not linked email addresses') %>

+ +<% end %> + + + + + + + + + + + + <% for channel in @channels: %> + + + + + + + <% if channel.status_in is 'error': %> + + + + <% end %> + <% if channel.status_out is 'error': %> + + + + <% end %> +<% end %> + +
<%- @T('Inbound') %><%- @T('Outbound') %><%- @T('Email Adresses') %><%- @T('Action') %>
+ <%- @T('State') %>: <%- @T(channel.status_in || 'unknown') %>
+ <%= channel.options.inbound.options.user %>
+ <%= channel.options.inbound.options.host %> (<%= channel.options.inbound.adapter %>) +
+ <%- @T('State') %>: <%- @T(channel.status_out || 'unknown') %>
+ <% if channel.options.outbound && channel.options.outbound.options: %> + <%= channel.options.outbound.options.user %>
+ <%= channel.options.outbound.options.host %> + <% end %> + (<%= channel.options.outbound.adapter %>) +
+
    + <% if !_.isEmpty(channel.email_addresses): %> + <% for email_address in channel.email_addresses: %> +
  • <%= email_address.email %> + <% end %> + <% else: %> +
  • <%- @T('none') %>
  • + <% end %> +
+ +
+ +
<%= channel.last_log_in %>
<%= channel.last_log_out %>
+ +<%- @T('New') %> + +

<%- @T('Notification Account') %>

+ + + + + + + + + + + + <% if @channel.status_in is 'error': %> + + + + <% end %> + <% if @channel.status_out is 'error': %> + + + + <% end %> + +
<%- @T('Outbound') %>
+ <%- @T('State') %>: <%- @T(@channel.status_out || 'unknown') %>
+ <% if @channel.options.outbound && @channel.options.outbound.options: %> + <%= @channel.options.outbound.options.user %>
+ <%= @channel.options.outbound.options.host %> + <% end %> + (<%= @channel.options.outbound.adapter %>) +
<%= @channel.last_log_in %>
<%= @channel.last_log_out %>
\ No newline at end of file diff --git a/app/assets/javascripts/app/views/channel/email_account_wizard.jst.eco b/app/assets/javascripts/app/views/channel/email_account_wizard.jst.eco new file mode 100644 index 000000000..e30300144 --- /dev/null +++ b/app/assets/javascripts/app/views/channel/email_account_wizard.jst.eco @@ -0,0 +1,92 @@ + diff --git a/app/assets/javascripts/app/views/channel/email_notification_wizard.jst.eco b/app/assets/javascripts/app/views/channel/email_notification_wizard.jst.eco new file mode 100644 index 000000000..111ebb985 --- /dev/null +++ b/app/assets/javascripts/app/views/channel/email_notification_wizard.jst.eco @@ -0,0 +1,39 @@ + diff --git a/app/assets/javascripts/app/views/channel/email_outbound.jst.eco b/app/assets/javascripts/app/views/channel/email_outbound.jst.eco deleted file mode 100644 index a6bd44073..000000000 --- a/app/assets/javascripts/app/views/channel/email_outbound.jst.eco +++ /dev/null @@ -1,5 +0,0 @@ -
-
-
-
+ + + + +
+
+

<%- @T('Email Notification') %>

+
+

+ <%- @T('Verifying...') %> +

+
+
+
+ + \ No newline at end of file diff --git a/app/controllers/channels_controller.rb b/app/controllers/channels_controller.rb index bb0dc9fac..5d6b32221 100644 --- a/app/controllers/channels_controller.rb +++ b/app/controllers/channels_controller.rb @@ -11,14 +11,25 @@ JSON Example: { "id":1, - "area":"Email::Inbound", - "adapter":"IMAP", + "area":"Email::Account", "group_id:": 1, "options":{ - "host":"mail.example.com", - "user":"some_user", - "password":"some_password", - "ssl":true + "inbound": { + "adapter":"IMAP", + "options": { + "host":"mail.example.com", + "user":"some_user", + "password":"some_password", + "ssl":true + }, + "outbound":{ + "adapter":"SMTP", + "options": { + "host":"mail.example.com", + "user":"some_user", + "password":"some_password", + "start_tls":true + } }, "active":true, "updated_at":"2012-09-14T17:51:53Z", @@ -29,10 +40,10 @@ Example: { "id":1, - "area":"Twitter::Inbound", - "adapter":"Twitter", + "area":"Twitter::Account", "group_id:": 1, "options":{ + "adapter":"Twitter", "auth": { "consumer_key":"PJ4c3dYYRtSZZZdOKo8ow", "consumer_secret":"ggAdnJE2Al1Vv0cwwvX5bdvKOieFs0vjCIh5M8Dxk", @@ -84,14 +95,12 @@ Response: [ { "id": 1, - "area":"Email::Inbound", - "adapter":"IMAP", + "area":"Email::Account", ... }, { "id": 2, - "area":"Email::Inbound", - "adapter":"IMAP", + "area":"Email::Account", ... } ] @@ -114,8 +123,7 @@ GET /api/v1/channels/#{id}.json Response: { "id": 1, - "area":"Email::Inbound", - "adapter":"IMAP", + "area":"Email::Account", ... } @@ -136,22 +144,33 @@ POST /api/v1/channels.json Payload: { - "area":"Email::Inbound", - "adapter":"IMAP", + "area":"Email::Account", "group_id:": 1, "options":{ - "host":"mail.example.com", - "user":"some_user", - "password":"some_password", - "ssl":true + "inbound": + "adapter":"IMAP", + "options":{ + "host":"mail.example.com", + "user":"some_user", + "password":"some_password", + "ssl":true + }, + }, + "outbound":{ + "adapter":"SMTP", + "options": { + "host":"mail.example.com", + "user":"some_user", + "password":"some_password", + "start_tls":true + } }, "active":true, } Response: { - "area":"Email::Inbound", - "adapter":"IMAP", + "area":"Email::Account", ... } @@ -173,14 +192,26 @@ PUT /api/v1/channels/{id}.json Payload: { "id":1, - "area":"Email::Inbound", - "adapter":"IMAP", + "area":"Email::Account", "group_id:": 1, "options":{ - "host":"mail.example.com", - "user":"some_user", - "password":"some_password", - "ssl":true + "inbound": + "adapter":"IMAP", + "options":{ + "host":"mail.example.com", + "user":"some_user", + "password":"some_password", + "ssl":true + }, + }, + "outbound":{ + "adapter":"SMTP", + "options": { + "host":"mail.example.com", + "user":"some_user", + "password":"some_password", + "start_tls":true + } }, "active":true, } @@ -219,4 +250,207 @@ curl http://localhost/api/v1/channels.json -v -u #{login}:#{password} -H "Conten return if deny_if_not_role(Z_ROLENAME_ADMIN) model_destory_render(Channel, params) end + + def email_index + return if deny_if_not_role(Z_ROLENAME_ADMIN) + + assets = {} + Channel.all.each {|channel| + assets = channel.assets(assets) + } + EmailAddress.all.each {|email_address| + assets = email_address.assets(assets) + } + render json: { + assets: assets + } + end + + def email_probe + + # check admin permissions + return if deny_if_not_role(Z_ROLENAME_ADMIN) + + # probe settings based on email and password + result = EmailHelper::Probe.full( + email: params[:email], + password: params[:password], + ) + + # verify if user+host already exists + if result[:result] == 'ok' + return if email_account_duplicate?(result) + end + + render json: result + end + + def email_outbound + + # check admin permissions + return if deny_if_not_role(Z_ROLENAME_ADMIN) + + # connection test + render json: EmailHelper::Probe.outbound(params, params[:email]) + end + + def email_inbound + + # check admin permissions + return if deny_if_not_role(Z_ROLENAME_ADMIN) + + # connection test + result = EmailHelper::Probe.inbound(params) + + # check account duplicate + return if email_account_duplicate?({ setting: { inbound: params } }, params[:channel_id]) + + render json: result + end + + def email_verify + + # check admin permissions + return if deny_if_not_role(Z_ROLENAME_ADMIN) + + email = params[:email] || params[:meta][:email] + email = email.downcase + channel_id = params[:channel_id] + + # check account duplicate + return if email_account_duplicate?({ setting: { inbound: params[:inbound] } }, channel_id) + + # check delivery for 30 sek. + result = EmailHelper::Verify.email( + outbound: params[:outbound], + inbound: params[:inbound], + sender: email, + subject: params[:subject], + ) + + if result[:result] != 'ok' + render json: result + return + end + + # update account + if channel_id + channel = Channel.find(channel_id) + channel.update_attributes( + options: { + inbound: params[:inbound], + outbound: params[:outbound], + }, + last_log_in: '', + last_log_out: '', + status_in: nil, + status_out: nil, + ) + render json: { + result: 'ok', + } + return + end + + # create new account + channel = Channel.create( + area: 'Email::Account', + options: { + inbound: params[:inbound], + outbound: params[:outbound], + }, + active: true, + group_id: Group.first.id, + ) + + # remember address && set channel for email address + address = EmailAddress.find_by(email: email) + + # if we are on initial setup, use already exisiting dummy email address + if Channel.count == 1 + address = EmailAddress.first + end + + if address + address.update_attributes( + realname: params[:meta][:realname], + email: email, + active: true, + channel_id: channel.id, + ) + else + address = EmailAddress.create( + realname: params[:meta][:realname], + email: email, + active: true, + channel_id: channel.id, + ) + end + + render json: { + result: 'ok', + } + end + + def email_notification + + # check admin permissions + return if deny_if_not_role(Z_ROLENAME_ADMIN) + + adapter = params[:adapter].downcase + + email = Setting.get('notification_sender') + + # connection test + result = EmailHelper::Probe.outbound(params, email) + + # save settings + if result[:result] == 'ok' + + # validate adapter + if adapter !~ /^(smtp|sendmail)$/ + render json: { + result: 'failed', + message: "Unknown adapter '#{adapter}'", + } + return + end + + Channel.where(area: 'Email::Notification').each {|channel| + active = false + if adapter =~ /^#{channel.options[:outbound][:adapter]}$/i + active = true + channel.options = { + outbound: { + adapter: adapter, + options: params[:options], + }, + } + end + channel.active = active + channel.save + } + end + render json: result + end + + private + + def email_account_duplicate?(result, channel_id = nil) + Channel.where(area: 'Email::Account').each {|channel| + next if !channel.options + next if !channel.options[:inbound] + next if !channel.options[:inbound][:adapter] + next if channel.options[:inbound][:adapter] != result[:setting][:inbound][:adapter] + next if channel.options[:inbound][:options][:host] != result[:setting][:inbound][:options][:host] + next if channel.options[:inbound][:options][:user] != result[:setting][:inbound][:options][:user] + next if channel.id.to_s == channel_id.to_s + render json: { + result: 'duplicate', + message: 'Account already exists!', + } + return true + } + false + end end diff --git a/app/controllers/getting_started_controller.rb b/app/controllers/getting_started_controller.rb index 3dc56d4d6..e38d18efb 100644 --- a/app/controllers/getting_started_controller.rb +++ b/app/controllers/getting_started_controller.rb @@ -188,119 +188,6 @@ curl http://localhost/api/v1/getting_started -v -u #{login}:#{password} } end - def email_probe - - # check admin permissions - return if deny_if_not_role(Z_ROLENAME_ADMIN) - - # probe settings based on email and password - render json: EmailHelper::Probe.full( - email: params[:email], - password: params[:password], - ) - end - - def email_outbound - - # check admin permissions - return if deny_if_not_role(Z_ROLENAME_ADMIN) - - # connection test - render json: EmailHelper::Probe.outbound(params, params[:email]) - end - - def email_inbound - - # check admin permissions - return if deny_if_not_role(Z_ROLENAME_ADMIN) - - # connection test - render json: EmailHelper::Probe.inbound(params) - end - - def email_verify - - # check admin permissions - return if deny_if_not_role(Z_ROLENAME_ADMIN) - - # send verify email to inbox - if !params[:subject] - subject = '#' + rand(99_999_999_999).to_s - else - subject = params[:subject] - end - - result = EmailHelper::Verify.email( - outbound: params[:outbound], - inbound: params[:inbound], - sender: params[:meta][:email], - subject: subject, - ) - - # check delivery for 30 sek. - if result[:result] != 'ok' - render json: result - return - end - - # remember address - address = EmailAddress.where( email: params[:meta][:email] ).first - if !address - address = EmailAddress.first - end - if address - address.update_attributes( - realname: params[:meta][:realname], - email: params[:meta][:email], - active: 1, - updated_by_id: 1, - created_by_id: 1, - ) - else - EmailAddress.create( - realname: params[:meta][:realname], - email: params[:meta][:email], - active: 1, - updated_by_id: 1, - created_by_id: 1, - ) - end - - # store mailbox - Channel.create( - area: 'Email::Inbound', - adapter: params[:inbound][:adapter], - options: params[:inbound][:options], - group_id: 1, - active: 1, - updated_by_id: 1, - created_by_id: 1, - ) - - # save settings - if params[:outbound][:adapter] =~ /^smtp$/i - smtp = Channel.where( adapter: 'SMTP', area: 'Email::Outbound' ).first - smtp.options = params[:outbound][:options] - smtp.active = true - smtp.save! - sendmail = Channel.where( adapter: 'Sendmail' ).first - sendmail.active = false - sendmail.save! - else - sendmail = Channel.where( adapter: 'Sendmail', area: 'Email::Outbound' ).first - sendmail.options = {} - sendmail.active = true - sendmail.save! - smtp = Channel.where( adapter: 'SMTP' ).first - smtp.active = false - smtp.save - end - - render json: { - result: 'ok', - } - end - private def auto_wizard_enabled_response diff --git a/app/models/channel.rb b/app/models/channel.rb index 12fa1dc87..a806f6101 100644 --- a/app/models/channel.rb +++ b/app/models/channel.rb @@ -3,24 +3,107 @@ class Channel < ApplicationModel store :options - def self.fetch - channels = Channel.where( 'active = ? AND area LIKE ?', true, '%::Inbound' ) - channels.each { |channel| - begin - # we need to require each channel backend individually otherwise we get a - # 'warning: toplevel constant Twitter referenced by Channel::Twitter' error e.g. - # so we have to convert the channel name to the filename via Rails String.underscore - # http://stem.ps/rails/2015/01/25/ruby-gotcha-toplevel-constant-referenced-by.html - require "channel/#{channel[:adapter].to_filename}" +=begin - channel_object = Object.const_get("Channel::#{channel[:adapter].to_classname}") - channel_instance = channel_object.new - channel_instance.fetch(channel) - rescue => e - logger.error "Can't use Channel::#{channel[:adapter].to_classname}" - logger.error e.inspect - logger.error e.backtrace - end - } +fetch all accounts + + Channel.fetch + +=end + + def self.fetch + channels = Channel.where('active = ? AND area LIKE ?', true, '%::Account') + channels.each(&:fetch) end + +=begin + +fetch one account + + channel = Channel.where(area: 'Email::Account').first + channel.fetch + +=end + + def fetch + + adapter = options[:adapter] + adapter_options = options + if options[:options] + adapter_options = options[:options] + elsif options[:inbound] && options[:inbound][:adapter] + adapter = options[:inbound][:adapter] + adapter_options = options[:inbound][:options] + end + + begin + + # we need to require each channel backend individually otherwise we get a + # 'warning: toplevel constant Twitter referenced by Channel::Driver::Twitter' error e.g. + # so we have to convert the channel name to the filename via Rails String.underscore + # http://stem.ps/rails/2015/01/25/ruby-gotcha-toplevel-constant-referenced-by.html + require "channel/driver/#{adapter.to_filename}" + + driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}") + driver_instance = driver_class.new + driver_instance.fetch(adapter_options, self) + self.status_in = 'ok' + self.last_log_in = '' + save + rescue => e + error = "Can't use Channel::Driver::#{adapter.to_classname}: #{e.inspect}" + logger.error error + logger.error e.backtrace + self.status_in = 'error' + self.last_log_in = error + save + end + + end + +=begin + +send via account + + channel = Channel.where(area: 'Email::Account').first + channel.deliver(mail_params, notification) + +=end + + def deliver(mail_params, notification = false) + + adapter = options[:adapter] + adapter_options = options + if options[:options] + adapter_options = options[:options] + elsif options[:outbound] && options[:outbound][:adapter] + adapter = options[:outbound][:adapter] + adapter_options = options[:outbound][:options] + end + + begin + + # we need to require each channel backend individually otherwise we get a + # 'warning: toplevel constant Twitter referenced by Channel::Driver::Twitter' error e.g. + # so we have to convert the channel name to the filename via Rails String.underscore + # http://stem.ps/rails/2015/01/25/ruby-gotcha-toplevel-constant-referenced-by.html + require "channel/driver/#{adapter.to_filename}" + + driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}") + driver_instance = driver_class.new + driver_instance.send(adapter_options, mail_params, notification) + self.status_out = 'ok' + self.last_log_out = '' + save + rescue => e + error = "Can't use Channel::Driver::#{adapter.to_classname}: #{e.inspect}" + logger.error error + logger.error e.backtrace + self.status_out = 'error' + self.last_log_out = error + save + end + + end + end diff --git a/app/models/channel/driver.rb b/app/models/channel/driver.rb new file mode 100644 index 000000000..fe4f9321e --- /dev/null +++ b/app/models/channel/driver.rb @@ -0,0 +1,4 @@ +class Channel + class Driver + end +end diff --git a/app/models/channel/facebook.rb b/app/models/channel/driver/facebook.rb similarity index 93% rename from app/models/channel/facebook.rb rename to app/models/channel/driver/facebook.rb index 2afb09a9a..5308e66eb 100644 --- a/app/models/channel/facebook.rb +++ b/app/models/channel/driver/facebook.rb @@ -2,9 +2,9 @@ require 'facebook' -class Channel::Facebook +class Channel::Driver::Facebook - def fetch (channel) + def fetch (adapter_options, channel) @channel = channel @facebook = Facebook.new( @channel[:options] ) diff --git a/app/models/channel/imap.rb b/app/models/channel/driver/imap.rb similarity index 75% rename from app/models/channel/imap.rb rename to app/models/channel/driver/imap.rb index d0a00f65a..1c59b7fe0 100644 --- a/app/models/channel/imap.rb +++ b/app/models/channel/driver/imap.rb @@ -2,17 +2,17 @@ require 'net/imap' -class Channel::Imap < Channel::EmailParser +class Channel::Driver::Imap < Channel::EmailParser - def fetch (channel, check_type = '', verify_string = '') + def fetch (options, channel, check_type = '', verify_string = '') ssl = true port = 993 - if channel[:options].key?(:ssl) && channel[:options][:ssl].to_s == 'false' + if options.key?(:ssl) && options[:ssl].to_s == 'false' ssl = false port = 143 end - Rails.logger.info "fetching imap (#{channel[:options][:host]}/#{channel[:options][:user]} port=#{port},ssl=#{ssl})" + Rails.logger.info "fetching imap (#{options[:host]}/#{options[:user]} port=#{port},ssl=#{ssl})" # on check, reduce open_timeout to have faster probing timeout = 12 @@ -22,24 +22,24 @@ class Channel::Imap < Channel::EmailParser Timeout.timeout(timeout) do - @imap = Net::IMAP.new( channel[:options][:host], port, ssl, nil, false ) + @imap = Net::IMAP.new( options[:host], port, ssl, nil, false ) end # try LOGIN, if not - try plain begin - @imap.authenticate( 'LOGIN', channel[:options][:user], channel[:options][:password] ) + @imap.authenticate( 'LOGIN', options[:user], options[:password] ) rescue => e if e.to_s !~ /(unsupported\s(authenticate|authentication)\smechanism|not\ssupported)/i raise e end - @imap.login( channel[:options][:user], channel[:options][:password] ) + @imap.login( options[:user], options[:password] ) end - if !channel[:options][:folder] || channel[:options][:folder].empty? + if !options[:folder] || options[:folder].empty? @imap.select('INBOX') else - @imap.select( channel[:options][:folder] ) + @imap.select( options[:folder] ) end if check_type == 'check' Rails.logger.info 'check only mode, fetch no emails' diff --git a/app/models/channel/mail_stdin.rb b/app/models/channel/driver/mail_stdin.rb similarity index 77% rename from app/models/channel/mail_stdin.rb rename to app/models/channel/driver/mail_stdin.rb index c3316980b..06808f512 100644 --- a/app/models/channel/mail_stdin.rb +++ b/app/models/channel/driver/mail_stdin.rb @@ -1,6 +1,6 @@ # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ -class Channel::MailStdin < Channel::EmailParser +class Channel::Driver::MailStdin < Channel::EmailParser def initialize Rails.logger.info 'read main from STDIN' diff --git a/app/models/channel/pop3.rb b/app/models/channel/driver/pop3.rb similarity index 77% rename from app/models/channel/pop3.rb rename to app/models/channel/driver/pop3.rb index d96d85c31..253f9b098 100644 --- a/app/models/channel/pop3.rb +++ b/app/models/channel/driver/pop3.rb @@ -2,19 +2,19 @@ require 'net/pop' -class Channel::Pop3 < Channel::EmailParser +class Channel::Driver::Pop3 < Channel::EmailParser - def fetch (channel, check_type = '', verify_string = '') + def fetch (options, channel, check_type = '', verify_string = '') ssl = true port = 995 - if channel[:options].key?(:ssl) && channel[:options][:ssl].to_s == 'false' + if options.key?(:ssl) && options[:ssl].to_s == 'false' ssl = false port = 110 end - Rails.logger.info "fetching pop3 (#{channel[:options][:host]}/#{channel[:options][:user]} port=#{port},ssl=#{ssl})" + Rails.logger.info "fetching pop3 (#{options[:host]}/#{options[:user]} port=#{port},ssl=#{ssl})" - @pop = Net::POP3.new( channel[:options][:host], port ) + @pop = Net::POP3.new( options[:host], port ) # on check, reduce open_timeout to have faster probing if check_type == 'check' @@ -25,7 +25,7 @@ class Channel::Pop3 < Channel::EmailParser if ssl @pop.enable_ssl(OpenSSL::SSL::VERIFY_NONE) end - @pop.start( channel[:options][:user], channel[:options][:password] ) + @pop.start( options[:user], options[:password] ) if check_type == 'check' Rails.logger.info 'check only mode, fetch no emails' disconnect diff --git a/app/models/channel/sendmail.rb b/app/models/channel/driver/sendmail.rb similarity index 77% rename from app/models/channel/sendmail.rb rename to app/models/channel/driver/sendmail.rb index e0ce4cc93..d275363ae 100644 --- a/app/models/channel/sendmail.rb +++ b/app/models/channel/driver/sendmail.rb @@ -1,7 +1,7 @@ # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ -class Channel::Sendmail - def send(attr, _channel, notification = false) +class Channel::Driver::Sendmail + def send(_options, attr, notification = false) # return if we run import mode return if Setting.get('import_mode') diff --git a/app/models/channel/smtp.rb b/app/models/channel/driver/smtp.rb similarity index 55% rename from app/models/channel/smtp.rb rename to app/models/channel/driver/smtp.rb index 4cf901858..9ae7e2dcd 100644 --- a/app/models/channel/smtp.rb +++ b/app/models/channel/driver/smtp.rb @@ -1,7 +1,7 @@ # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ -class Channel::SMTP - def send(attr, channel, notification = false) +class Channel::Driver::Smtp + def send(options, attr, notification = false) # return if we run import mode return if Setting.get('import_mode') @@ -9,11 +9,11 @@ class Channel::SMTP mail = Channel::EmailBuild.build(attr, notification) mail.delivery_method :smtp, { openssl_verify_mode: 'none', - address: channel[:options][:host], - port: channel[:options][:port] || 25, - domain: channel[:options][:host], - user_name: channel[:options][:user], - password: channel[:options][:password], + address: options[:host], + port: options[:port] || 25, + domain: options[:host], + user_name: options[:user], + password: options[:password], enable_starttls_auto: true, } mail.deliver diff --git a/app/models/channel/twitter.rb b/app/models/channel/driver/twitter.rb similarity index 94% rename from app/models/channel/twitter.rb rename to app/models/channel/driver/twitter.rb index cf4d8fb10..dda9340df 100644 --- a/app/models/channel/twitter.rb +++ b/app/models/channel/driver/twitter.rb @@ -1,8 +1,8 @@ # Copyright (C) 2012-2015 Zammad Foundation, http://zammad-foundation.org/ -class Channel::Twitter +class Channel::Driver::Twitter - def fetch (channel) + def fetch (adapter_options, channel) @channel = channel @tweet = Tweet.new( @channel[:options][:auth] ) @@ -21,7 +21,7 @@ class Channel::Twitter def send(article, _notification = false) - @channel = Channel.find_by( area: 'Twitter::Inbound', active: true ) + @channel = Channel.find_by( area: 'Twitter::Account', active: true ) @tweet = Tweet.new( @channel[:options][:auth] ) tweet = @tweet.from_article(article) diff --git a/app/models/channel/email_send.rb b/app/models/channel/email_send.rb deleted file mode 100644 index 0cc7b6b66..000000000 --- a/app/models/channel/email_send.rb +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ - -require 'net/imap' - -module Channel::EmailSend - def self.send(article, notification = false) - channel = Channel.find_by( area: 'Email::Outbound', active: true ) - begin - # we need to require the channel backend individually otherwise we get a - # 'warning: toplevel constant Twitter referenced by Channel::Twitter' error e.g. - # so we have to convert the channel name to the filename via Rails String.underscore - # http://stem.ps/rails/2015/01/25/ruby-gotcha-toplevel-constant-referenced-by.html - require "channel/#{channel[:adapter].underscore}" - - channel_object = Object.const_get("Channel::#{channel[:adapter]}") - channel_instance = channel_object.new - - result = channel_instance.send(article, channel, notification) - - channel_instance.disconnect - rescue => e - Rails.logger.error "Can't use Channel::#{channel[:adapter]}" - Rails.logger.error e.inspect - Rails.logger.error e.backtrace - end - result - end -end diff --git a/app/models/email_address.rb b/app/models/email_address.rb index b64cdbd8b..0545eef3e 100644 --- a/app/models/email_address.rb +++ b/app/models/email_address.rb @@ -2,6 +2,7 @@ class EmailAddress < ApplicationModel has_many :groups, after_add: :cache_update, after_remove: :cache_update + belongs_to :channel validates :realname, presence: true validates :email, presence: true diff --git a/app/models/observer/ticket/article/communicate_email/background_job.rb b/app/models/observer/ticket/article/communicate_email/background_job.rb index 4d396fcc1..6873ebf94 100644 --- a/app/models/observer/ticket/article/communicate_email/background_job.rb +++ b/app/models/observer/ticket/article/communicate_email/background_job.rb @@ -11,7 +11,15 @@ class Observer::Ticket::Article::CommunicateEmail::BackgroundJob subject = ticket.subject_build( record.subject ) # send email - message = Channel::EmailSend.send( + if !ticket.group.email_address_id + fail "Can't send email, no email address definde for group id '#{ticket.group.id}'" + elsif !ticket.group.email_address.channel_id + fail "Can't send email, no channel definde for email_address id '#{ticket.group.email_address_id}'" + end + channel = ticket.group.email_address.channel + + # get linked channel and send + message = channel.deliver( { message_id: record.message_id, in_reply_to: record.in_reply_to, diff --git a/app/models/observer/ticket/article/communicate_facebook.rb b/app/models/observer/ticket/article/communicate_facebook.rb index 54b4578ee..41a89eadb 100644 --- a/app/models/observer/ticket/article/communicate_facebook.rb +++ b/app/models/observer/ticket/article/communicate_facebook.rb @@ -1,6 +1,6 @@ # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ -require 'channel/facebook' +require 'channel/driver/facebook' class Observer::Ticket::Article::CommunicateFacebook < ActiveRecord::Observer observe 'ticket::_article' @@ -19,7 +19,7 @@ class Observer::Ticket::Article::CommunicateFacebook < ActiveRecord::Observer type = Ticket::Article::Type.lookup( id: record.type_id ) return if type['name'] !~ /\Afacebook/ - facebook = Channel::Facebook.new + facebook = Channel::Driver::Facebook.new post = facebook.send({ type: type['name'], to: record.to, diff --git a/app/models/observer/ticket/article/communicate_twitter.rb b/app/models/observer/ticket/article/communicate_twitter.rb index fe1660644..c809e5783 100644 --- a/app/models/observer/ticket/article/communicate_twitter.rb +++ b/app/models/observer/ticket/article/communicate_twitter.rb @@ -1,7 +1,7 @@ # Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/ # http://stem.ps/rails/2015/01/25/ruby-gotcha-toplevel-constant-referenced-by.html -require 'channel/twitter' +require 'channel/driver/twitter' class Observer::Ticket::Article::CommunicateTwitter < ActiveRecord::Observer observe 'ticket::_article' @@ -20,7 +20,7 @@ class Observer::Ticket::Article::CommunicateTwitter < ActiveRecord::Observer type = Ticket::Article::Type.lookup( id: record.type_id ) return if type['name'] !~ /\Atwitter/ - twitter = Channel::Twitter.new + twitter = Channel::Driver::Twitter.new tweet = twitter.send({ type: type['name'], to: record.to, diff --git a/config/routes/channel.rb b/config/routes/channel.rb index 7d7c2adc8..6c3c1f610 100644 --- a/config/routes/channel.rb +++ b/config/routes/channel.rb @@ -1,6 +1,14 @@ Zammad::Application.routes.draw do api_path = Rails.configuration.api_path + # email helper + match api_path + '/channels/email_index', to: 'channels#email_index', via: :get + match api_path + '/channels/email_probe', to: 'channels#email_probe', via: :post + match api_path + '/channels/email_outbound', to: 'channels#email_outbound', via: :post + match api_path + '/channels/email_inbound', to: 'channels#email_inbound', via: :post + match api_path + '/channels/email_verify', to: 'channels#email_verify', via: :post + match api_path + '/channels/email_notification', to: 'channels#email_notification', via: :post + # channels match api_path + '/channels', to: 'channels#index', via: :get match api_path + '/channels/:id', to: 'channels#show', via: :get diff --git a/config/routes/getting_started.rb b/config/routes/getting_started.rb index 7597256d5..4ebd6c60c 100644 --- a/config/routes/getting_started.rb +++ b/config/routes/getting_started.rb @@ -2,13 +2,9 @@ Zammad::Application.routes.draw do api_path = Rails.configuration.api_path # getting_started - match api_path + '/getting_started', to: 'getting_started#index', via: :get - match api_path + '/getting_started/auto_wizard/:token', to: 'getting_started#auto_wizard_admin', via: :get - match api_path + '/getting_started/auto_wizard', to: 'getting_started#auto_wizard_admin', via: :get - match api_path + '/getting_started/base', to: 'getting_started#base', via: :post - match api_path + '/getting_started/email_probe', to: 'getting_started#email_probe', via: :post - match api_path + '/getting_started/email_outbound', to: 'getting_started#email_outbound', via: :post - match api_path + '/getting_started/email_inbound', to: 'getting_started#email_inbound', via: :post - match api_path + '/getting_started/email_verify', to: 'getting_started#email_verify', via: :post + match api_path + '/getting_started', to: 'getting_started#index', via: :get + match api_path + '/getting_started/auto_wizard/:token', to: 'getting_started#auto_wizard_admin', via: :get + match api_path + '/getting_started/auto_wizard', to: 'getting_started#auto_wizard_admin', via: :get + match api_path + '/getting_started/base', to: 'getting_started#base', via: :post end diff --git a/db/migrate/20150824000002_update_channel.rb b/db/migrate/20150824000002_update_channel.rb new file mode 100644 index 000000000..a58305690 --- /dev/null +++ b/db/migrate/20150824000002_update_channel.rb @@ -0,0 +1,57 @@ +class UpdateChannel < ActiveRecord::Migration + def up + + add_column :email_addresses, :channel_id, :integer, null: true + EmailAddress.reset_column_information + + channel = Channel.find_by(area: 'Email::Inbound') + EmailAddress.all.each {|email_address| + email_address.channel_id = channel.id + email_address.save + } + + add_column :channels, :last_log_in, :text, limit: 500.kilobytes + 1, null: true + add_column :channels, :last_log_out, :text, limit: 500.kilobytes + 1, null: true + add_column :channels, :status_in, :string, limit: 100, null: true + add_column :channels, :status_out, :string, limit: 100, null: true + Channel.reset_column_information + + channel_outbound = Channel.find_by(area: 'Email::Outbound', active: true) + + Channel.all.each {|channel| + if channel.area == 'Email::Inbound' + channel.area = 'Email::Account' + options = { + inbound: { + adapter: channel.adapter.downcase, + options: channel.options, + }, + outbound: { + adapter: channel_outbound.adapter.downcase, + options: channel_outbound.options, + }, + } + channel.options = options + channel.save + elsif channel.area == 'Email::Outbound' + channel.area = 'Email::Notification' + options = { + outbound: { + adapter: channel.adapter, + options: channel.options, + }, + } + channel.options = options + channel.save + elsif channel.area == 'Twitter::Inbound' + channel.area = 'Twitter::Account' + channel.options[:adapter] = channel.adapter + channel.save + end + } + + remove_column :channels, :adapter + Channel.reset_column_information + + end +end diff --git a/db/seeds.rb b/db/seeds.rb index 5c2b6d362..f5f5601bd 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1743,21 +1743,28 @@ Overview.create_if_not_exists( ) Channel.create_if_not_exists( - adapter: 'SMTP', - area: 'Email::Outbound', + area: 'Email::Notification', options: { - host: 'host.example.com', - user: '', - password: '', - ssl: true, + outbound: { + adapter: 'smtp', + options: { + host: 'host.example.com', + user: '', + password: '', + ssl: true, + }, + }, }, group_id: 1, active: false, ) Channel.create_if_not_exists( - adapter: 'Sendmail', - area: 'Email::Outbound', - options: {}, + area: 'Email::Notification', + options: { + outbound: { + adapter: 'sendmail', + }, + }, active: true, ) diff --git a/lib/email_helper/probe.rb b/lib/email_helper/probe.rb index 163d88831..abb2c8db4 100644 --- a/lib/email_helper/probe.rb +++ b/lib/email_helper/probe.rb @@ -14,26 +14,28 @@ returns on success { result: 'ok', - inbound: { - adapter: 'imap', - options: { - host: 'imap.gmail.com', - port: 993, - ssl: true, - user: 'some@example.com', - password: 'password', + settings: { + inbound: { + adapter: 'imap', + options: { + host: 'imap.gmail.com', + port: 993, + ssl: true, + user: 'some@example.com', + password: 'password', + }, }, - }, - outbound: { - adapter: 'smtp', - options: { - host: 'smtp.gmail.com', - port: 25, - ssl: true, - user: 'some@example.com', - password: 'password', + outbound: { + adapter: 'smtp', + options: { + host: 'smtp.gmail.com', + port: 25, + ssl: true, + user: 'some@example.com', + password: 'password', + }, }, - }, + } } returns on fail @@ -150,7 +152,7 @@ get result of inbound probe result = EmailHelper::Probe.inbound( adapter: 'imap', - options: { + settings: { host: 'imap.gmail.com', port: 993, ssl: true, @@ -184,24 +186,22 @@ returns on fail def self.inbound(params) - # validate params - if !params[:adapter] - result = { - result: 'invalid', - message: 'Invalid, need adapter!', - } - return result - end + adapter = params[:adapter].downcase # connection test begin - if params[:adapter] =~ /^imap$/i - Channel::Imap.new.fetch( { options: params[:options] }, 'check' ) - elsif params[:adapter] =~ /^pop3$/i - Channel::Pop3.new.fetch( { options: params[:options] }, 'check' ) - else - fail "Invalid adapter '#{params[:adapter]}'" + + # validate adapter + if adapter !~ /^(imap|pop3)$/ + fail "Unknown adapter '#{adapter}'" end + + require "channel/driver/#{adapter.to_filename}" + + driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}") + driver_instance = driver_class.new + driver_instance.fetch(params[:options], nil, 'check') + rescue => e message_human = '' translations.each {|key, message| @@ -238,7 +238,8 @@ get result of outbound probe password: 'password', } }, - 'sender@example.com', + 'sender_and_recipient_of_test_email@example.com', + 'subject of probe email', ) returns on success @@ -266,15 +267,9 @@ returns on fail def self.outbound(params, email, subject = nil) - # validate params - if !params[:adapter] - result = { - result: 'invalid', - message: 'Invalid, need adapter!', - } - return result - end + adapter = params[:adapter].downcase + # prepare test email if subject mail = { :from => email, @@ -295,29 +290,29 @@ returns on fail # test connection begin - if params[:adapter] =~ /^smtp$/i + # validate adapter + if adapter !~ /^(smtp|sendmail)$/ + fail "Unknown adapter '#{adapter}'" + end - # in case, fill missing params + # set smtp defaults + if adapter =~ /^smtp$/ if !params[:options].key?(:port) params[:options][:port] = 25 end if !params[:options].key?(:ssl) params[:options][:ssl] = true end - Channel::SMTP.new.send( - mail, - { - options: params[:options] - } - ) - elsif params[:adapter] =~ /^sendmail$/i - Channel::Sendmail.new.send( - mail, - nil - ) - else - fail "Invalid adapter '#{params[:adapter]}'" end + + require "channel/driver/#{adapter.to_filename}" + + driver_class = Object.const_get("Channel::Driver::#{adapter.to_classname}") + driver_instance = driver_class.new + driver_instance.send( + params[:options], + mail, + ) rescue => e # check if sending email was ok, but mailserver rejected diff --git a/lib/email_helper/verify.rb b/lib/email_helper/verify.rb index 1a1536d4e..d5d3f251a 100644 --- a/lib/email_helper/verify.rb +++ b/lib/email_helper/verify.rb @@ -26,7 +26,7 @@ get result of inbound probe password: 'password', }, }, - sender: 'sender@example.com', + sender: 'sender_and_recipient_of_verify_email@example.com', ) returns on success @@ -56,12 +56,13 @@ or def self.email(params) # send verify email - if !params[:subject] + if !params[:subject] || params[:subject].empty? subject = '#' + rand(99_999_999_999).to_s else subject = params[:subject] end - result = EmailHelper::Probe.outbound( params[:outbound], params[:sender], subject ) + puts "VERIFY #{subject.inspect}/#{params[:sender]}" + result = EmailHelper::Probe.outbound(params[:outbound], params[:sender], subject) # looking for verify email (1..5).each { @@ -72,9 +73,9 @@ or begin if params[:inbound][:adapter] =~ /^imap$/i - found = Channel::Imap.new.fetch( { options: params[:inbound][:options] }, 'verify', subject ) + found = Channel::Driver::Imap.new.fetch(params[:inbound][:options], self, 'verify', subject) else - found = Channel::Pop3.new.fetch( { options: params[:inbound][:options] }, 'verify', subject ) + found = Channel::Driver::Pop3.new.fetch(params[:inbound][:options], self, 'verify', subject) end rescue => e result = { diff --git a/lib/notification_factory.rb b/lib/notification_factory.rb index 8fda5a610..c9cd3ff1b 100644 --- a/lib/notification_factory.rb +++ b/lib/notification_factory.rb @@ -111,7 +111,9 @@ module NotificationFactory content_type = data[:content_type] end - Channel::EmailSend.send( + # get active Email::Outbound Channel and send + channel = Channel.find_by(area: 'Email::Notification', active: true) + channel.deliver( { # in_reply_to: in_reply_to, from: sender, diff --git a/public/assets/tests/table.js b/public/assets/tests/table.js index 3975f053a..3889dfbcb 100644 --- a/public/assets/tests/table.js +++ b/public/assets/tests/table.js @@ -370,6 +370,19 @@ test( "table test 2", function() { created_at: '2014-06-10T11:17:34.000Z', }, ]) + + App.Channel.configure_delete = true + App.Channel.configure_attributes = [ + { name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: { IMAP: 'IMAP', POP3: 'POP3' } }, + { name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false }, + { name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false }, + { name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false }, + { name: 'options::ssl', display: 'SSL', tag: 'select', multiple: false, null: true, options: { true: 'yes', false: 'no' }, translate: true, default: true}, + { name: 'options::folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false }, + { name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: false, nulloption: true, relation: 'Group' }, + { name: 'active', display: 'Active', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' }, translate: true, default: true }, + ] + App.Channel.refresh( [ { id: 1, diff --git a/test/browser/aaa_getting_started_test.rb b/test/browser/aaa_getting_started_test.rb index 9f444a64f..20d331efc 100644 --- a/test/browser/aaa_getting_started_test.rb +++ b/test/browser/aaa_getting_started_test.rb @@ -44,13 +44,9 @@ class AaaGettingStartedTest < TestCase value: 'test1234äöüß', ) click( css: '.js-admin .btn--success' ) - watch_for( - css: '.js-base h2', - value: 'Organization', - ) # getting started - base - match( + watch_for( css: '.js-base h2', value: 'Organization', ) @@ -76,19 +72,27 @@ class AaaGettingStartedTest < TestCase click( css: '.js-base .btn--primary', ) + + # getting started - email notification watch_for( - css: 'body', - value: 'channel', + css: '.js-outbound h2', + value: 'Email Notification', ) location_check( - url: '#getting_started/channel', + url: '#getting_started/email_notification', + ) + click( + css: '.js-outbound .btn--primary', ) # getting started - create email account - match( + watch_for( css: '.js-channel h2', value: 'Connect Channels', ) + location_check( + url: '#getting_started/channel', + ) click( css: '.js-channel .email .provider_name', ) @@ -117,14 +121,10 @@ class AaaGettingStartedTest < TestCase value: 'invite', timeout: 100, ) - location_check( - url: '#getting_started/agents', - ) # invite agent1 - match( - css: 'body', - value: 'Invite', + location_check( + url: '#getting_started/agents', ) set( css: '.js-agent input[name="firstname"]', diff --git a/test/integration/facebook_test.rb b/test/integration/facebook_test.rb index 6e118ea02..8722d7363 100644 --- a/test/integration/facebook_test.rb +++ b/test/integration/facebook_test.rb @@ -20,6 +20,7 @@ class FacebookTest < ActiveSupport::TestCase provider_page_name = 'Hansi Merkurs Hutfabrik' provider_options = { + adapter: 'facebook', auth: { access_token: provider_key }, @@ -34,8 +35,7 @@ class FacebookTest < ActiveSupport::TestCase current = Channel.where( adapter: 'Facebook' ) current.each(&:destroy) Channel.create( - adapter: 'Facebook', - area: 'Facebook::Inbound', + area: 'Facebook::Account', options: provider_options, active: true, created_by_id: 1, diff --git a/test/integration/twitter_test.rb b/test/integration/twitter_test.rb index b0c427945..13f2f881a 100644 --- a/test/integration/twitter_test.rb +++ b/test/integration/twitter_test.rb @@ -28,12 +28,12 @@ class TwitterTest < ActiveSupport::TestCase me_bauer_token_secret = 'T8ph5afeSDjGDA9X1ZBlzEvoSiXfN266ZZUMj5UaY' # add channel - current = Channel.where( adapter: 'Twitter' ) + current = Channel.where(area: 'Twitter::Account') current.each(&:destroy) Channel.create( - adapter: 'Twitter', - area: 'Twitter::Inbound', + area: 'Twitter::Account', options: { + adapter: 'twitter', auth: { consumer_key: consumer_key, consumer_secret: consumer_secret,