diff --git a/.gitignore b/.gitignore index 1f771fe..2a9bae4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,9 @@ /.bundle /.idea /config/database.yml -/config/secrets.yml +/config/service.yml +/config/secret.yml + # Ignore the default SQLite database. /db/*.sqlite3 @@ -19,6 +21,11 @@ /tmp/* !/log/.keep !/tmp/.keep +/README.html # Ignore Byebug command history file. .byebug_history + +# Ignore RVM files. +/.ruby-gemset +/.ruby-version diff --git a/.hound.yml b/.hound.yml new file mode 100644 index 0000000..b7945ab --- /dev/null +++ b/.hound.yml @@ -0,0 +1,3 @@ +ruby: + enabled: true + config_file: .rubocop.yml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..e283273 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,96 @@ +AllCops: + TargetRubyVersion: 2.3 + DisplayCopNames: true + + Exclude: + - 'db/schema.rb' + +Layout/AccessModifierIndentation: + EnforcedStyle: indent + +Layout/MultilineMethodCallIndentation: + EnforcedStyle: indented + +Layout/SpaceInsideArrayLiteralBrackets: + Exclude: + - 'config/**/*' + +Lint/AssignmentInCondition: + Enabled: false + +Lint/ReturnInVoidContext: + Enabled: false + +Metrics/AbcSize: + Enabled: false + +Metrics/BlockLength: + Exclude: + - 'config/routes.rb' + - 'spec/**/*' + +Metrics/LineLength: + Max: 120 + +Metrics/MethodLength: + Enabled: false + +Naming/AccessorMethodName: + Enabled: true + +Rails: + Enabled: true + +Rails/Delegate: + Exclude: + - 'lib/**/*' + +Rails/FilePath: + Enabled: false + +Rails/HttpPositionalArguments: + Enabled: false + +Rails/SkipsModelValidations: + Exclude: + - 'db/migrate/**/*' + - 'spec/**/*' + +Rails/UnknownEnv: + Environments: + - development + - test + - production + +Style/AndOr: + EnforcedStyle: conditionals + +Style/ClassAndModuleChildren: + Enabled: false + +Style/Documentation: + Enabled: false + +Style/DoubleNegation: + Enabled: false + +Style/FormatString: + Enabled: false + +Style/FrozenStringLiteralComment: + Enabled: false + +Style/IfUnlessModifier: + Enabled: false + +Style/MixinUsage: + Enabled: false + +Style/RescueModifier: + Enabled: true + +Style/RescueStandardError: + EnforcedStyle: implicit + +Bundler/OrderedGems: + Enabled: false diff --git a/Gemfile b/Gemfile index 1d715cb..35fdc69 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,6 @@ source 'https://rubygems.org' ruby '2.3.1' gem 'rails', '~> 5.0.0', '>= 5.0.0.1' -gem 'sqlite3' # Use Puma as the app server gem 'puma', '~> 3.0' @@ -11,6 +10,8 @@ gem 'httparty' gem 'sass-rails', '~> 5.0' gem 'uglifier', '>= 1.3.0' gem 'coffee-rails', '~> 4.2' +gem 'aws-sdk', '~> 2' +gem 'giphy' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console @@ -25,3 +26,7 @@ group :development do gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' end + +group :test do + gem 'rubocop', '~> 0.58.0', require: false +end diff --git a/Gemfile.lock b/Gemfile.lock index e0bb13f..648cc28 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,45 +1,56 @@ GEM remote: https://rubygems.org/ specs: - actioncable (5.0.0.1) - actionpack (= 5.0.0.1) - nio4r (~> 1.2) + actioncable (5.0.2) + actionpack (= 5.0.2) + nio4r (>= 1.2, < 3.0) websocket-driver (~> 0.6.1) - actionmailer (5.0.0.1) - actionpack (= 5.0.0.1) - actionview (= 5.0.0.1) - activejob (= 5.0.0.1) + actionmailer (5.0.2) + actionpack (= 5.0.2) + actionview (= 5.0.2) + activejob (= 5.0.2) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.0.0.1) - actionview (= 5.0.0.1) - activesupport (= 5.0.0.1) + actionpack (5.0.2) + actionview (= 5.0.2) + activesupport (= 5.0.2) rack (~> 2.0) rack-test (~> 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.0.1) - activesupport (= 5.0.0.1) + actionview (5.0.2) + activesupport (= 5.0.2) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (5.0.0.1) - activesupport (= 5.0.0.1) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.0.2) + activesupport (= 5.0.2) globalid (>= 0.3.6) - activemodel (5.0.0.1) - activesupport (= 5.0.0.1) - activerecord (5.0.0.1) - activemodel (= 5.0.0.1) - activesupport (= 5.0.0.1) + activemodel (5.0.2) + activesupport (= 5.0.2) + activerecord (5.0.2) + activemodel (= 5.0.2) + activesupport (= 5.0.2) arel (~> 7.0) - activesupport (5.0.0.1) + activesupport (5.0.2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) minitest (~> 5.1) tzinfo (~> 1.1) + addressable (2.5.1) + public_suffix (~> 2.0, >= 2.0.2) arel (7.1.4) - builder (3.2.2) + ast (2.4.0) + aws-sdk (2.9.0) + aws-sdk-resources (= 2.9.0) + aws-sdk-core (2.9.0) + aws-sigv4 (~> 1.0) + jmespath (~> 1.0) + aws-sdk-resources (2.9.0) + aws-sdk-core (= 2.9.0) + aws-sigv4 (1.0.0) + builder (3.2.3) byebug (9.0.6) coffee-rails (4.2.1) coffee-script (>= 2.2.0) @@ -47,17 +58,34 @@ GEM coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.10.0) - concurrent-ruby (1.0.2) + coffee-script-source (1.12.2) + concurrent-ruby (1.0.5) debug_inspector (0.0.2) erubis (2.7.0) execjs (2.7.0) - ffi (1.9.14) + faraday (0.12.0.1) + multipart-post (>= 1.2, < 3) + faraday_middleware (0.11.0.1) + faraday (>= 0.7.4, < 1.0) + faraday_middleware-parse_oj (0.3.1) + faraday (~> 0.9) + faraday_middleware (>= 0.9.1, < 1.0) + oj (~> 2.0) + ffi (1.9.18) + giphy (3.0.0) + faraday (~> 0.9) + faraday_middleware (~> 0.9) + faraday_middleware-parse_oj (~> 0.3) + launchy (~> 2.4) globalid (0.3.7) activesupport (>= 4.1.0) httparty (0.14.0) multi_xml (>= 0.5.2) - i18n (0.7.0) + i18n (0.8.1) + jaro_winkler (1.5.1) + jmespath (1.3.1) + launchy (2.4.3) + addressable (~> 2.3) listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -70,75 +98,92 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) mini_portile2 (2.1.0) - minitest (5.9.1) - multi_xml (0.5.5) - nio4r (1.2.1) - nokogiri (1.6.8.1) + minitest (5.10.1) + multi_xml (0.6.0) + multipart-post (2.0.0) + nio4r (2.0.0) + nokogiri (1.7.1) mini_portile2 (~> 2.1.0) - puma (3.6.1) + oj (2.18.5) + parallel (1.12.1) + parser (2.5.1.0) + ast (~> 2.4.0) + powerpack (0.1.2) + public_suffix (2.0.5) + puma (3.8.2) rack (2.0.1) rack-test (0.6.3) rack (>= 1.0) - rails (5.0.0.1) - actioncable (= 5.0.0.1) - actionmailer (= 5.0.0.1) - actionpack (= 5.0.0.1) - actionview (= 5.0.0.1) - activejob (= 5.0.0.1) - activemodel (= 5.0.0.1) - activerecord (= 5.0.0.1) - activesupport (= 5.0.0.1) + rails (5.0.2) + actioncable (= 5.0.2) + actionmailer (= 5.0.2) + actionpack (= 5.0.2) + actionview (= 5.0.2) + activejob (= 5.0.2) + activemodel (= 5.0.2) + activerecord (= 5.0.2) + activesupport (= 5.0.2) bundler (>= 1.3.0, < 2.0) - railties (= 5.0.0.1) + railties (= 5.0.2) sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.1) + rails-dom-testing (2.0.2) activesupport (>= 4.2.0, < 6.0) - nokogiri (~> 1.6.0) + nokogiri (~> 1.6) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - railties (5.0.0.1) - actionpack (= 5.0.0.1) - activesupport (= 5.0.0.1) + railties (5.0.2) + actionpack (= 5.0.2) + activesupport (= 5.0.2) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rake (11.3.0) + rainbow (3.0.0) + rake (12.0.0) rb-fsevent (0.9.8) - rb-inotify (0.9.7) + rb-inotify (0.9.8) ffi (>= 0.5.0) - sass (3.4.22) + rubocop (0.58.0) + jaro_winkler (~> 1.5.1) + parallel (~> 1.10) + parser (>= 2.5) + powerpack (~> 0.1) + rainbow (>= 2.2.2, < 4.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) + ruby-progressbar (1.9.0) + sass (3.4.23) sass-rails (5.0.6) railties (>= 4.0.0, < 6) sass (~> 3.1) sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) - spring (2.0.0) + spring (2.0.1) activesupport (>= 4.2) spring-watcher-listen (2.0.1) listen (>= 2.7, < 4.0) spring (>= 1.2, < 3.0) - sprockets (3.7.0) + sprockets (3.7.1) concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.2.0) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sqlite3 (1.3.12) - thor (0.19.1) - thread_safe (0.3.5) - tilt (2.0.5) + thor (0.19.4) + thread_safe (0.3.6) + tilt (2.0.7) tzinfo (1.2.2) thread_safe (~> 0.1) - uglifier (3.0.3) + uglifier (3.1.9) execjs (>= 0.3.0, < 3) + unicode-display_width (1.4.0) web-console (3.4.0) actionview (>= 5.0) activemodel (>= 5.0) debug_inspector railties (>= 5.0) - websocket-driver (0.6.4) + websocket-driver (0.6.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.2) @@ -146,16 +191,18 @@ PLATFORMS ruby DEPENDENCIES + aws-sdk (~> 2) byebug coffee-rails (~> 4.2) + giphy httparty listen (~> 3.0.5) puma (~> 3.0) rails (~> 5.0.0, >= 5.0.0.1) + rubocop (~> 0.58.0) sass-rails (~> 5.0) spring spring-watcher-listen (~> 2.0.0) - sqlite3 uglifier (>= 1.3.0) web-console @@ -163,4 +210,4 @@ RUBY VERSION ruby 2.3.1p112 BUNDLED WITH - 1.12.5 + 1.16.2 diff --git a/README.md b/README.md index 9b3c2f2..76bd899 100644 --- a/README.md +++ b/README.md @@ -2,35 +2,84 @@ Rails application to transfer messages to your basecamp3 HQ. -## Supported services +### Supported services 1. [AWS SNS](https://aws.amazon.com/sns/) +2. [Bugsnag](https://www.bugsnag.com/) +3. [GitLab](https://gitlab.com) +4. [Rollbar](https://rollbar.com) +5. [Giphy](https://giphy.com) -## Usage +### Instruction -1. Register AWS and [create SNS topic](http://docs.aws.amazon.com/sns/latest/dg/CreateTopic.html). Remember Topic ARN! It looks like this: - `arn:aws:sns:us-east-6:23730808936387:topick_name` -2. Register Basecamp and [create bot](https://m.signalvnoise.com/new-in-basecamp-3-chatbots-8526618c0c7d#.kabo3hgs1). There +* [Create a Chatbot](#create-chatbot ) +* [AWS integration](#aws-integration) +* [Bugsnag integration](#bugsnag-integration) +* [Gitlab integration](#gitlab-integration) +* [Rollbar integration](#rollbar-integration) +* [Giphy integration](#giphy-integration) +* [References](#references) + +#### Create a Chatbot + +1. Register Basecamp and [create bot](https://m.signalvnoise.com/new-in-basecamp-3-chatbots-8526618c0c7d#.kabo3hgs1). There you need a bot long link. Like this: `https://3.basecamp.com/195539477/integrations/2uH9aHLEVhhaXKPaqrj8yw8P/buckets/2085958501/chats/9007199254741775/lines` -3. Clone this repo from GitHub. +2. Clone this repo from GitHub. 1. Run __`bundle install`__ in your project directory. 2. Create a ```secrets.yml``` file in your config folder as described [here](http://guides.rubyonrails.org/upgrading_ruby_on_rails.html#config-secrets-yml). -4. Write your SNS **Topic ARN** and Basecamp **bot url** to /config/service.yml -5. Start rails server by typing `rails s` command. -6. [Create subscription](http://docs.aws.amazon.com/sns/latest/dg/SubscribeTopic.html). Use HTTP **Protocol** and `http://your-external-host/api/v1/messages` as **Endpoint** -7. You will probably want to try running your app locally to test it and make sure it’s working as expected. To do that you will need a service that allows you to expose a web server running on your local machine to the Internet. We recommend to try [Ngrok](http://ngrok.com). +3. Add your Basecamp __bot url__ to `/config/service.yml` +4. You will probably want to try running your app locally to test it and make sure it’s working as expected. To do that you will need a service that allows you to expose a web server running on your local machine to the Internet. We recommend to try [Ngrok](http://ngrok.com). 1. If you see this message on rails console: ``Cannot render console from XXX.XXX.XXX.XXX! Allowed networks: 127.0.0.1,...`` You need to whitelist the XXX.XXX.XXX.XXX network space in the Web console config. - Open ``config/environments/development.rb`` and type your local IP address from the Internet Service Provider: + Open ``config/environments/development.rb`` and type IP address of remote server: ``` config.web_console.whitelisted_ips = 'XXX.XXX.XXX.XXX' ``` Read more about IP's whitelisting [here](https://github.com/rails/web-console#configweb_consolewhitelisted_ips). -8. At first time SNS send confirmation request. After subscription you can -[publish messages to a topic](http://docs.aws.amazon.com/sns/latest/dg/PublishTopic.html) +#### AWS integration + +1. Register AWS and [create SNS topic](http://docs.aws.amazon.com/sns/latest/dg/CreateTopic.html). Remember Topic ARN! It looks like this: + `arn:aws:sns:us-east-6:23730808936387:topick_name` +2. Add your SNS __Topic ARN__ to `/config/service.yml` +3. [Aws-sdk gem](https://github.com/aws/aws-sdk-ruby), which used in this App, needs 'aws_access_key' and 'aws_secret_key' to work. To find them navigate to "My Security Credentials" category in your asw.amazon account. Always load your credentials from outside your application! Look at `/config/service.yml` for example. Avoid configuring credentials statically and never commit them to source control! + 1) If you're running on AWS EC2 you able to assign AWS keys dynamically with IAM roles. + 2. Create an IAM role. + 3. Define which AWS services can assume the role (AmazonSNSFullAccess etc.) + 4. Specify the role when you launch your instance, or attach the role to a running or stopped instance. +4. Start rails server by typing `rails s` command. +5. [Create subscription](http://docs.aws.amazon.com/sns/latest/dg/SubscribeTopic.html). Use HTTP **Protocol** and `http://your-external-host/api/sns/messages` as __Endpoint__ +6. At first time SNS send confirmation request which will be automatically accepted. After subscription you can [publish messages to the topic](http://docs.aws.amazon.com/sns/latest/dg/PublishTopic.html) + +#### Bugsnag integration + +1. Create [Bugsnag account](https://www.bugsnag.com/) and create new project. +2. Navigate to 'Integrations' menu. +3. Select the 'Webhook', enter the full URL where webhooks should be posted (http://your-external-host/api/bugsnag/messages`), choice notification events and enable the integration. +4. For more instructions, please visit [Bugsnag docs](https://docs.bugsnag.com/api/) and [Ruby on Rails integration](https://docs.bugsnag.com/platforms/ruby/rails/) + +#### Gitlab integration + +1. Create [Gitlab account](https://gitlab.com) and start the New project. +2. Navigate to the project's 'Settings/Integrations' menu. +3. Select the 'Webhook', enter the full URL where webhooks should be posted (http://your-external-host/api/gitlab/messages`). Mark event checkboxes and click the 'Add Webhook' button. +4. For more instructions, please visit [GitLab Webhooks docs](https://gitlab.com/help/user/project/integrations/webhooks). + +#### Rollbar integration + +1. Create [Rollbar account](https://rollbar.com) and start the New project. +2. Navigate to the project, click 'Settings', then click 'Notifications' in the left menu. +3. Select the 'Webhook', enter the full URL where webhooks should be posted (this App server) and enable the integration. +4. Once set up, you can add, edit, or remove rules. For more instructions, please visit [Rollbar docs](https://rollbar.com/docs/webhooks) and [Ruby Integration](https://rollbar.com/docs/notifier/rollbar-gem) + +#### Giphy integration + +1. Add Gipy API 'search query' to `/config/service.yml`. Look at `/config/service.yml` for example. +2. If you'll send some POST request to `http://your-external-host/api/giphy/messages` Chatbot will post to your Basecamp chat a random funny gif from [Giphy](http://giphy.com/). +3. Just for fun! -## References +#### References -* Most controller logic based on [Creating SNS subscription endpoints with Ruby on Rails](http://blog.eng.xogrp.com/post/79166302844/creating-sns-subscription-endpoints-with-ruby-on#disqus_thread) +* AWS-SDK [documentation](http://docs.aws.amazon.com/sdkforruby/api/Aws/SNS/Client.html) +* Giphy [Api docs](https://github.com/Giphy/GiphyAPI) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index b12018d..e54c646 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -10,7 +10,4 @@ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details // about supported directives. // -//= require jquery -//= require jquery_ujs -//= require turbolinks //= require_tree . diff --git a/app/controllers/api/bugsnag/messages_controller.rb b/app/controllers/api/bugsnag/messages_controller.rb new file mode 100644 index 0000000..4b6e787 --- /dev/null +++ b/app/controllers/api/bugsnag/messages_controller.rb @@ -0,0 +1,34 @@ +class Api::Bugsnag::MessagesController < ApplicationController + def create + # Get the parsed JSON string + bugsnag_parsed = Chatbot.parse_webhook(request.body.read) + + # error: JSON:ParserError + # Get the failed JSON request message + request_error = bugsnag_parsed[:error] + + # Prepare message for the campfire + message = if request_error + "Failed or non-JSON request: #{request_error}" + else + build_message_text bugsnag_parsed + end + + # send message to basecamp + Chatbot.send_message(command_params[:callback_url], message) + end + + private + + def build_message_text(bugsnag_parsed) + # Store parsed dada from Bugsnag + event = bugsnag_parsed['error']['exceptionClass'] + error_message = bugsnag_parsed['error']['message'] + event_url = bugsnag_parsed['error']['url'] + "Event: #{event}
+ Message: #{error_message}
+ Bugsnag report: #{event_url}" + rescue NoMethodError => e # del. exception handlidg to see errors in console + "Bugsnag parsing error: #{e}" + end +end diff --git a/app/controllers/api/giphy/messages_controller.rb b/app/controllers/api/giphy/messages_controller.rb new file mode 100644 index 0000000..c6fab45 --- /dev/null +++ b/app/controllers/api/giphy/messages_controller.rb @@ -0,0 +1,6 @@ +class Api::Giphy::MessagesController < ApplicationController + def create + image = Giphy.random(command_params[:command]).image_original_url.to_s + Chatbot.send_message(command_params[:callback_url], "") + end +end diff --git a/app/controllers/api/gitlab/messages_controller.rb b/app/controllers/api/gitlab/messages_controller.rb new file mode 100644 index 0000000..f8b1052 --- /dev/null +++ b/app/controllers/api/gitlab/messages_controller.rb @@ -0,0 +1,34 @@ +class Api::Gitlab::MessagesController < ApplicationController + def create + # Get the parsed JSON string + gitlab_parsed = Chatbot.parse_webhook(request.body.read) + + # error: JSON:ParserError + # Get the failed JSON request message + request_error = gitlab_parsed[:error] + + # Prepare message for the campfire + message = if request_error + "Failed or non-JSON request:#{request_error}" + else + build_message_text gitlab_parsed + end + + # send message to basecamp + Chatbot.send_message(command_params[:callback_url], message) + end + + private + + def build_message_text(gitlab_parsed) + # Store parsed dada from GitLab + project_name = gitlab_parsed['project']['name'] + event = gitlab_parsed['object_kind'] + project_url = gitlab_parsed['project']['web_url'] + "Gitlab project: #{project_name}
+ Event: #{event}
+ Project url: #{project_url}" + rescue NoMethodError => e # del. exception handlidg to see errors in console + "GitLab parsing error: #{e}" + end +end diff --git a/app/controllers/api/rollbar/messages_controller.rb b/app/controllers/api/rollbar/messages_controller.rb new file mode 100644 index 0000000..71fe9ea --- /dev/null +++ b/app/controllers/api/rollbar/messages_controller.rb @@ -0,0 +1,35 @@ +class Api::Rollbar::MessagesController < ApplicationController + def create + # Get the parsed JSON string + rollbar_parsed = Chatbot.parse_webhook(request.body.read) + + # error: JSON:ParserError + # Get the failed JSON request message + request_error = rollbar_parsed[:error] + + # Prepare message for the campfire + message = if request_error + "Failed or non-JSON request: #{request_error}" + else + build_message_text rollbar_parsed + end + + # send message to basecamp + Chatbot.send_message(command_params[:callback_url], message) + end + + private + + def build_message_text(rollbar_parsed) + # Store parsed dada from Rollbar + event = rollbar_parsed['event_name'] + data = rollbar_parsed['data']['item']['title'] + uuid = rollbar_parsed['data']['occurrence']['uuid'] + event_url = "https://rollbar.com/instance/uuid?uuid=#{uuid}" + "Event: #{event}
+ Body: #{data}
+ Rollbar report: #{event_url}" + rescue NoMethodError => e # del. exception handlidg to see errors in console + "Rollbar parsing error: #{e}" + end +end diff --git a/app/controllers/api/sns/messages_controller.rb b/app/controllers/api/sns/messages_controller.rb new file mode 100644 index 0000000..622fc68 --- /dev/null +++ b/app/controllers/api/sns/messages_controller.rb @@ -0,0 +1,32 @@ +class Api::Sns::MessagesController < ApplicationController + def create + amazon_sns_request = Chatbot.parse_webhook(request.body.read) + snstopic_arn = Rails.configuration.service['snstopic_arn'] + aws_region = Rails.configuration.service['aws_region'] + client = Aws::SNS::Client.new(region: aws_region) + + if amazon_sns_request['Type'].to_s.casecmp('SubscriptionConfirmation') >= 0 + client.confirm_subscription( + topic_arn: snstopic_arn, + token: amazon_sns_request['Token'] + ) + elsif amazon_sns_request['Type'].to_s.casecmp('Notification') >= 0 + message = build_message_text amazon_sns_request + end + + # send message to basecamp + Chatbot.send_message(command_params[:callback_url], message) + end + + private + + def build_message_text(amazon_sns_request) + # Store parsed dada from amazon SNS + subject = amazon_sns_request['Subject'] + body = amazon_sns_request['Message'] + "Subject: #{subject}
+ Body: #{body}
" + rescue NoMethodError => e # del. exception handlidg to see errors in console + "Amazon SNS parsing error: #{e}" + end +end diff --git a/app/controllers/api/v1/messages_controller.rb b/app/controllers/api/v1/messages_controller.rb deleted file mode 100644 index fb570d1..0000000 --- a/app/controllers/api/v1/messages_controller.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'json' -require 'httparty' - -class Api::V1::MessagesController < ApplicationController - skip_before_action :verify_authenticity_token - - def create - basecampbot_url = Rails.configuration.service['basecampbot_url'] - snstopic_arn = Rails.configuration.service['snstopic_arn'] - - # get amazon message type and topic - amz_message_type = request.headers['x-amz-sns-message-type'] - amz_sns_topic = request.headers['x-amz-sns-topic-arn'] - - # sure you recieve messages from a right topic - return unless !amz_sns_topic.nil? && - amz_sns_topic.to_s.downcase == snstopic_arn - - request_body = JSON.parse request.body.read - - # if this is the first time confirmation of subscription, then confirm it - if amz_message_type.to_s.downcase == 'subscriptionconfirmation' - send_subscription_confirmation request_body - return - end - - if amz_message_type.to_s.downcase == 'notification' - # DO WORK HERE - message = "Subject: #{request_body['Subject']}
#{request_body['Message']}" - # send message to basecamp - HTTParty.post basecampbot_url, query: { content: message } - end - - head :ok - end - - private - - def send_subscription_confirmation(request_body) - subscribe_url = request_body['SubscribeURL'] - return nil unless !subscribe_url.to_s.empty? && !subscribe_url.nil? - subscribe_confirm = HTTParty.get subscribe_url - end - - -end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1c07694..07c405a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,14 @@ class ApplicationController < ActionController::Base protect_from_forgery with: :exception + skip_before_action :verify_authenticity_token, if: :json_request? + + private + + def json_request? + request.format.json? + end + + def command_params + params.permit(:command, :callback_url) + end end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb deleted file mode 100644 index 286b223..0000000 --- a/app/mailers/application_mailer.rb +++ /dev/null @@ -1,4 +0,0 @@ -class ApplicationMailer < ActionMailer::Base - default from: 'from@example.com' - layout 'mailer' -end diff --git a/app/models/application_record.rb b/app/models/application_record.rb deleted file mode 100644 index 10a4cba..0000000 --- a/app/models/application_record.rb +++ /dev/null @@ -1,3 +0,0 @@ -class ApplicationRecord < ActiveRecord::Base - self.abstract_class = true -end diff --git a/app/models/chatbot.rb b/app/models/chatbot.rb new file mode 100644 index 0000000..73c1ac9 --- /dev/null +++ b/app/models/chatbot.rb @@ -0,0 +1,13 @@ +class Chatbot + def self.parse_webhook(resp) + JSON.parse resp + # Handle exceptions in case of failed request + rescue JSON::ParserError => error + { error: error.to_s } + end + + def self.send_message(basecampbot_url, message) + basecampbot_url ||= Rails.configuration.service['basecampbot_url'] + HTTParty.post basecampbot_url, query: { content: message } + end +end diff --git a/bin/bundle b/bin/bundle index 66e9889..f19acf5 100755 --- a/bin/bundle +++ b/bin/bundle @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) load Gem.bin_path('bundler', 'bundle') diff --git a/bin/rails b/bin/rails index 5badb2f..7a8ff81 100755 --- a/bin/rails +++ b/bin/rails @@ -1,6 +1,6 @@ #!/usr/bin/env ruby begin - load File.expand_path('../spring', __FILE__) + load File.expand_path('spring', __dir__) rescue LoadError => e raise unless e.message.include?('spring') end diff --git a/bin/rake b/bin/rake index d87d5f5..0ba8c48 100755 --- a/bin/rake +++ b/bin/rake @@ -1,6 +1,6 @@ #!/usr/bin/env ruby begin - load File.expand_path('../spring', __FILE__) + load File.expand_path('spring', __dir__) rescue LoadError => e raise unless e.message.include?('spring') end diff --git a/bin/setup b/bin/setup index 03132c3..fd12165 100755 --- a/bin/setup +++ b/bin/setup @@ -4,7 +4,7 @@ require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = Pathname.new File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") diff --git a/bin/spring b/bin/spring index 9bc076b..1dbfad1 100755 --- a/bin/spring +++ b/bin/spring @@ -8,7 +8,7 @@ unless defined?(Spring) require 'bundler' lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) - if spring = lockfile.specs.detect { |spec| spec.name == "spring" } + if spring = lockfile.specs.detect { |spec| spec.name == 'spring' } Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path gem 'spring', spring.version require 'spring/binstub' diff --git a/bin/update b/bin/update index a8e4462..32326c7 100755 --- a/bin/update +++ b/bin/update @@ -4,7 +4,7 @@ require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = Pathname.new File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") diff --git a/config/environments/production.rb b/config/environments/production.rb index ae04772..f6d4943 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -65,10 +65,9 @@ # require 'syslog/logger' # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') - if ENV["RAILS_LOG_TO_STDOUT"].present? + if ENV['RAILS_LOG_TO_STDOUT'].present? logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end - end diff --git a/config/initializers/aws_sdk.rb b/config/initializers/aws_sdk.rb new file mode 100644 index 0000000..b9a6146 --- /dev/null +++ b/config/initializers/aws_sdk.rb @@ -0,0 +1,11 @@ +# aws_secret_file: path to *.yml file with AWS_ACCESS_KEY_ID and AWS_SECRET_KEY_ID if exist +aws_secret_file = Rails.configuration.service['aws_secret_file'] + +if aws_secret_file.present? + creds = YAML.safe_load(File.read(aws_secret_file)) + aws_creds = Aws::Credentials.new( + creds['aws_access_key'], + creds['aws_secret_key'] + ) + Aws.config.update(region: creds['aws_region'], credentials: aws_creds) +end diff --git a/config/initializers/giphy.rb b/config/initializers/giphy.rb new file mode 100644 index 0000000..428500c --- /dev/null +++ b/config/initializers/giphy.rb @@ -0,0 +1,3 @@ +Giphy::Configuration.configure do |config| + config.api_key = Rails.configuration.service['giphy_api_key'] +end diff --git a/config/puma.rb b/config/puma.rb index c7f311f..cc018a1 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -4,16 +4,16 @@ # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum, this matches the default thread size of Active Record. # -threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i +threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 }.to_i threads threads_count, threads_count # Specifies the `port` that Puma will listen on to receive requests, default is 3000. # -port ENV.fetch("PORT") { 3000 } +port ENV.fetch('PORT') { 3000 } # Specifies the `environment` that Puma will run in. # -environment ENV.fetch("RAILS_ENV") { "development" } +environment ENV.fetch('RAILS_ENV') { 'development' } # Specifies the number of `workers` to boot in clustered mode. # Workers are forked webserver processes. If using threads and workers together @@ -21,7 +21,7 @@ # Workers do not work on JRuby or Windows (both of which do not support # processes). # -# workers ENV.fetch("WEB_CONCURRENCY") { 2 } +# workers ENV.fetch('WEB_CONCURRENCY') { 2 } # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code diff --git a/config/routes.rb b/config/routes.rb index 49427a4..a5096a6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,8 +1,24 @@ Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html - namespace :api, defaults: {format: :json} do - namespace :v1 do + namespace :api, defaults: { format: :json } do + namespace :bugsnag do + resources :messages, only: :create + end + + namespace :gitlab do + resources :messages, only: :create + end + + namespace :giphy do + resources :messages, only: :create + end + + namespace :rollbar do + resources :messages, only: :create + end + + namespace :sns do resources :messages, only: :create end end diff --git a/config/secrets.yml b/config/secrets.yml new file mode 100644 index 0000000..e96a23b --- /dev/null +++ b/config/secrets.yml @@ -0,0 +1,10 @@ +development: + secret_key_base: c32dd50710233ac4673188552d83136e8a7ee50c718b8a52158bea0e76a0afaa665d0f332b101e6c4024d7d3c2e976c4f856a5b5969b09937c7c408fad77d408 + +test: + secret_key_base: 8d42e7fede4ae5d5678250ed920ffbe92b316d73615d070609670e3ef19e72877e077df603c95d54391b2eb1f87ce8d917c6e5e376c22e94b213fa5cea408240 + +# Do not keep production secrets in the repository, +# instead read values from the environment. +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> \ No newline at end of file diff --git a/config/service.yml b/config/service.yml index ae11439..822b30b 100644 --- a/config/service.yml +++ b/config/service.yml @@ -1,6 +1,13 @@ + +# Add this file to .gitignore right now !!! default: &default - basecampbot_url: https://3.basecamp.com/3601306/integrations/6Mc7Rhug2NkpDY53Qu2x7TGn/buckets/1925463/chats/294329172/lines - snstopic_arn: arn:aws:sns:eu-central-1:798348585423:basecamp-bot + basecampbot_url: https://3.basecamp.com/34334368/integrations/Kvre67sn895bakxFzJLqD99GDRe/buckets/3854557/chats/498331306/lines + snstopic_arn: arn:aws:sns:us-west-2:2303078387557:bascamp_chatbot + # Path to the .yml file that contains 'aws_access_key' and 'aws_secret_key'. Attention!!! Delete it, if you are using Amazon EC2 hosting + aws_secret_file: /home/user/aws.yml + aws_region: 'us-west-6' + # Giphy API url + public API key + giphy_api_key: "dc6zaTOxFJmzC" development: <<: *default @@ -9,4 +16,5 @@ test: <<: *default production: - <<: *default \ No newline at end of file + basecampbot_url: <%= ENV["BASECAMPBOT_URL"] %> + giphy_api_key: <%= ENV["GIPHY_API_KEY"] %> diff --git a/config/spring.rb b/config/spring.rb index c9119b4..9fa7863 100644 --- a/config/spring.rb +++ b/config/spring.rb @@ -1,6 +1,6 @@ -%w( +%w[ .ruby-version .rbenv-vars tmp/restart.txt tmp/caching-dev.txt -).each { |path| Spring.watch(path) } +].each { |path| Spring.watch(path) } diff --git a/app/models/concerns/.keep b/spec/.keep similarity index 100% rename from app/models/concerns/.keep rename to spec/.keep