slack-ruby-bot-server 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +2 -0
  5. data/.rubocop_todo.yml +45 -0
  6. data/.travis.yml +9 -0
  7. data/CHANGELOG.md +16 -0
  8. data/CONTRIBUTING.md +125 -0
  9. data/DEBUGGING.md +25 -0
  10. data/Gemfile +3 -0
  11. data/Guardfile +8 -0
  12. data/LICENSE +21 -0
  13. data/README.md +49 -0
  14. data/RELEASING.md +69 -0
  15. data/Rakefile +16 -0
  16. data/app.json +9 -0
  17. data/config/initializers/bson/object_id.rb +12 -0
  18. data/config/initializers/grape/sort_extension.rb +8 -0
  19. data/config/initializers/slack-ruby-bot/client.rb +17 -0
  20. data/config/initializers/slack-ruby-bot/commands/base.rb +21 -0
  21. data/images/new.png +0 -0
  22. data/images/slackbotserver.gif +0 -0
  23. data/images/slackbutton.gif +0 -0
  24. data/lib/slack-ruby-bot-server.rb +17 -0
  25. data/lib/slack-ruby-bot-server/api.rb +8 -0
  26. data/lib/slack-ruby-bot-server/api/endpoints.rb +3 -0
  27. data/lib/slack-ruby-bot-server/api/endpoints/root_endpoint.rb +22 -0
  28. data/lib/slack-ruby-bot-server/api/endpoints/status_endpoint.rb +16 -0
  29. data/lib/slack-ruby-bot-server/api/endpoints/teams_endpoint.rb +70 -0
  30. data/lib/slack-ruby-bot-server/api/helpers.rb +4 -0
  31. data/lib/slack-ruby-bot-server/api/helpers/cursor_helpers.rb +37 -0
  32. data/lib/slack-ruby-bot-server/api/helpers/error_helpers.rb +52 -0
  33. data/lib/slack-ruby-bot-server/api/helpers/pagination_parameters.rb +19 -0
  34. data/lib/slack-ruby-bot-server/api/helpers/sort_helpers.rb +53 -0
  35. data/lib/slack-ruby-bot-server/api/middleware.rb +44 -0
  36. data/lib/slack-ruby-bot-server/api/presenters.rb +9 -0
  37. data/lib/slack-ruby-bot-server/api/presenters/paginated_presenter.rb +38 -0
  38. data/lib/slack-ruby-bot-server/api/presenters/root_presenter.rb +44 -0
  39. data/lib/slack-ruby-bot-server/api/presenters/status_presenter.rb +40 -0
  40. data/lib/slack-ruby-bot-server/api/presenters/team_presenter.rb +24 -0
  41. data/lib/slack-ruby-bot-server/api/presenters/teams_presenter.rb +14 -0
  42. data/lib/slack-ruby-bot-server/app.rb +85 -0
  43. data/lib/slack-ruby-bot-server/info.rb +11 -0
  44. data/lib/slack-ruby-bot-server/models.rb +1 -0
  45. data/lib/slack-ruby-bot-server/models/team.rb +66 -0
  46. data/lib/slack-ruby-bot-server/rspec.rb +5 -0
  47. data/lib/slack-ruby-bot-server/rspec/fabricators/team.rb +5 -0
  48. data/lib/slack-ruby-bot-server/server.rb +21 -0
  49. data/lib/slack-ruby-bot-server/service.rb +81 -0
  50. data/lib/slack-ruby-bot-server/version.rb +3 -0
  51. data/public/favicon.ico +0 -0
  52. data/public/img/slack.png +0 -0
  53. data/public/index.html.erb +28 -0
  54. data/public/robots.txt +2 -0
  55. data/public/scripts/jquery-1.7.1.min.js +4 -0
  56. data/public/scripts/register.js +50 -0
  57. data/public/scripts/stats.js +14 -0
  58. data/public/scripts/url.min.js +1 -0
  59. data/sample_app/.rspec +3 -0
  60. data/sample_app/Gemfile +14 -0
  61. data/sample_app/Procfile +1 -0
  62. data/sample_app/Rakefile +10 -0
  63. data/sample_app/commands.rb +2 -0
  64. data/sample_app/commands/help.rb +19 -0
  65. data/sample_app/commands/whoami.rb +6 -0
  66. data/sample_app/config.ru +20 -0
  67. data/sample_app/config/mongoid.yml +27 -0
  68. data/sample_app/config/newrelic.yml +217 -0
  69. data/sample_app/spec/commands/help_spec.rb +14 -0
  70. data/sample_app/spec/commands/whoami_spec.rb +14 -0
  71. data/sample_app/spec/spec_helper.rb +25 -0
  72. data/script/console +9 -0
  73. data/slack-ruby-bot-server.gemspec +48 -0
  74. data/tasks/db.rake +44 -0
  75. metadata +523 -0
@@ -0,0 +1,44 @@
1
+ module SlackRubyBotServer
2
+ module Api
3
+ module Presenters
4
+ module RootPresenter
5
+ include Roar::JSON::HAL
6
+ include Roar::Hypermedia
7
+ include Grape::Roar::Representer
8
+
9
+ link :self do |opts|
10
+ "#{base_url(opts)}/api"
11
+ end
12
+
13
+ link :status do |opts|
14
+ "#{base_url(opts)}/api/status"
15
+ end
16
+
17
+ link :teams do |opts|
18
+ {
19
+ href: "#{base_url(opts)}/api/teams/#{link_params(Helpers::PaginationParameters::ALL, :active)}",
20
+ templated: true
21
+ }
22
+ end
23
+
24
+ link :team do |opts|
25
+ {
26
+ href: "#{base_url(opts)}/api/teams/{id}",
27
+ templated: true
28
+ }
29
+ end
30
+
31
+ private
32
+
33
+ def base_url(opts)
34
+ request = Grape::Request.new(opts[:env])
35
+ request.base_url
36
+ end
37
+
38
+ def link_params(*args)
39
+ "{?#{args.join(',')}}"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,40 @@
1
+ module SlackRubyBotServer
2
+ module Api
3
+ module Presenters
4
+ module StatusPresenter
5
+ include Roar::JSON::HAL
6
+ include Roar::Hypermedia
7
+ include Grape::Roar::Representer
8
+
9
+ link :self do |opts|
10
+ "#{base_url(opts)}/status"
11
+ end
12
+
13
+ property :teams_count
14
+ property :active_teams_count
15
+ property :ping
16
+
17
+ private
18
+
19
+ def ping
20
+ team = Team.asc(:_id).first
21
+ return unless team
22
+ team.ping!
23
+ end
24
+
25
+ def teams_count
26
+ Team.count
27
+ end
28
+
29
+ def active_teams_count
30
+ Team.active.count
31
+ end
32
+
33
+ def base_url(opts)
34
+ request = Grape::Request.new(opts[:env])
35
+ request.base_url
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,24 @@
1
+ module SlackRubyBotServer
2
+ module Api
3
+ module Presenters
4
+ module TeamPresenter
5
+ include Roar::JSON::HAL
6
+ include Roar::Hypermedia
7
+ include Grape::Roar::Representer
8
+
9
+ property :id, type: String, desc: 'Team ID.'
10
+ property :team_id, type: String, desc: 'Slack team ID.'
11
+ property :name, type: String, desc: 'Team name.'
12
+ property :domain, type: String, desc: 'Team domain.'
13
+ property :active, type: Boolean, desc: 'Team is active.'
14
+ property :created_at, type: DateTime, desc: 'Date/time when the team was created.'
15
+ property :updated_at, type: DateTime, desc: 'Date/time when the team was accepted, declined or canceled.'
16
+
17
+ link :self do |opts|
18
+ request = Grape::Request.new(opts[:env])
19
+ "#{request.base_url}/api/teams/#{id}"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ module SlackRubyBotServer
2
+ module Api
3
+ module Presenters
4
+ module TeamsPresenter
5
+ include Roar::JSON::HAL
6
+ include Roar::Hypermedia
7
+ include Grape::Roar::Representer
8
+ include PaginatedPresenter
9
+
10
+ collection :results, extend: TeamPresenter, as: :teams, embedded: true
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,85 @@
1
+ module SlackRubyBotServer
2
+ class App
3
+ def prepare!
4
+ silence_loggers!
5
+ check_mongodb_provider!
6
+ check_database!
7
+ create_indexes!
8
+ mark_teams_active!
9
+ migrate_from_single_team!
10
+ update_team_name_and_id!
11
+ purge_inactive_teams!
12
+ configure_global_aliases!
13
+ end
14
+
15
+ def self.instance
16
+ @instance ||= SlackRubyBotServer::App.new
17
+ end
18
+
19
+ private
20
+
21
+ def logger
22
+ @logger ||= begin
23
+ $stdout.sync = true
24
+ Logger.new(STDOUT)
25
+ end
26
+ end
27
+
28
+ def silence_loggers!
29
+ Mongoid.logger.level = Logger::INFO
30
+ Mongo::Logger.logger.level = Logger::INFO
31
+ end
32
+
33
+ def check_mongodb_provider!
34
+ return unless ENV['RACK_ENV'] == 'production'
35
+ fail "Missing ENV['MONGO_URL'], ENV['MONGOHQ_URI'] or ENV['MONGOLAB_URI']." unless ENV['MONGO_URL'] || ENV['MONGOHQ_URI'] || ENV['MONGOLAB_URI']
36
+ end
37
+
38
+ def check_database!
39
+ rc = Mongoid.default_client.command(ping: 1)
40
+ return if rc && rc.ok?
41
+ fail rc.documents.first['error'] || 'Unexpected error.'
42
+ rescue Exception => e
43
+ warn "Error connecting to MongoDB: #{e.message}"
44
+ raise e
45
+ end
46
+
47
+ def create_indexes!
48
+ ::Mongoid::Tasks::Database.create_indexes
49
+ end
50
+
51
+ def mark_teams_active!
52
+ Team.where(active: nil).update_all(active: true)
53
+ end
54
+
55
+ def update_team_name_and_id!
56
+ Team.active.where(team_id: nil).each do |team|
57
+ begin
58
+ auth = team.ping![:auth]
59
+ team.update_attributes!(team_id: auth['team_id'], name: auth['team'])
60
+ rescue StandardError => e
61
+ logger.warn "Error pinging team #{team.id}: #{e.message}."
62
+ team.set(active: false)
63
+ end
64
+ end
65
+ end
66
+
67
+ def migrate_from_single_team!
68
+ return unless ENV.key?('SLACK_API_TOKEN')
69
+ logger.info 'Migrating from env SLACK_API_TOKEN ...'
70
+ team = Team.find_or_create_from_env!
71
+ logger.info "Automatically migrated team: #{team}."
72
+ logger.warn "You should unset ENV['SLACK_API_TOKEN']."
73
+ end
74
+
75
+ def purge_inactive_teams!
76
+ Team.purge!
77
+ end
78
+
79
+ def configure_global_aliases!
80
+ SlackRubyBot.configure do |config|
81
+ config.aliases = ENV['SLACK_RUBY_BOT_ALIASES'].split(' ') if ENV['SLACK_RUBY_BOT_ALIASES']
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,11 @@
1
+ module SlackRubyBotServer
2
+ INFO = <<-EOS
3
+ BotServer #{SlackRubyBotServer::VERSION}
4
+
5
+ © 2016 Daniel Doubrovkine & Contributors, MIT License
6
+ https://twitter.com/dblockdotorg
7
+
8
+ Free Service at http://slack-ruby-bot-server.herokuapp.com
9
+ Open-Source at https://github.com/dblock/slack-ruby-bot-server
10
+ EOS
11
+ end
@@ -0,0 +1 @@
1
+ require 'slack-ruby-bot-server/models/team'
@@ -0,0 +1,66 @@
1
+ class Team
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+
5
+ SORT_ORDERS = ['created_at', '-created_at', 'updated_at', '-updated_at']
6
+
7
+ field :team_id, type: String
8
+ field :name, type: String
9
+ field :domain, type: String
10
+ field :token, type: String
11
+ field :active, type: Boolean, default: true
12
+
13
+ scope :active, -> { where(active: true) }
14
+
15
+ validates_uniqueness_of :token, message: 'has already been used'
16
+ validates_presence_of :token
17
+ validates_presence_of :team_id
18
+
19
+ def deactivate!
20
+ update_attributes!(active: false)
21
+ end
22
+
23
+ def activate!(token)
24
+ update_attributes!(active: true, token: token)
25
+ end
26
+
27
+ def to_s
28
+ {
29
+ name: name,
30
+ domain: domain,
31
+ id: team_id
32
+ }.map do |k, v|
33
+ "#{k}=#{v}" if v
34
+ end.compact.join(', ')
35
+ end
36
+
37
+ def ping!
38
+ client = Slack::Web::Client.new(token: token)
39
+ auth = client.auth_test
40
+ {
41
+ auth: auth,
42
+ presence: client.users_getPresence(user: auth['user_id'])
43
+ }
44
+ end
45
+
46
+ def self.find_or_create_from_env!
47
+ token = ENV['SLACK_API_TOKEN']
48
+ return unless token
49
+ team = Team.where(token: token).first
50
+ team ||= Team.new(token: token)
51
+ info = Slack::Web::Client.new(token: token).team_info
52
+ team.team_id = info['team']['id']
53
+ team.name = info['team']['name']
54
+ team.domain = info['team']['domain']
55
+ team.save!
56
+ team
57
+ end
58
+
59
+ def self.purge!
60
+ # destroy teams inactive for two weeks
61
+ Team.where(active: false, :updated_at.lte => 2.weeks.ago).each do |team|
62
+ Mongoid.logger.info "Destroying #{team}, inactive since #{team.updated_at}, over two weeks ago."
63
+ team.destroy
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,5 @@
1
+ require 'slack-ruby-bot/rspec'
2
+ require 'slack-ruby-bot-server'
3
+ require 'fabrication'
4
+ require 'faker'
5
+ require_relative 'rspec/fabricators/team'
@@ -0,0 +1,5 @@
1
+ Fabricator(:team) do
2
+ token { Fabricate.sequence(:team_token) { |i| "abc-#{i}" } }
3
+ team_id { Fabricate.sequence(:team_id) { |i| "T#{i}" } }
4
+ name { Faker::Lorem.word }
5
+ end
@@ -0,0 +1,21 @@
1
+ module SlackRubyBotServer
2
+ class Server < SlackRubyBot::Server
3
+ attr_accessor :team
4
+
5
+ def initialize(attrs = {})
6
+ @team = attrs[:team]
7
+ fail 'Missing team' unless @team
8
+ options = { token: @team.token }
9
+ super(options)
10
+ client.owner = @team
11
+ end
12
+
13
+ def restart!(wait = 1)
14
+ # when an integration is disabled, a live socket is closed, which causes the default behavior of the client to restart
15
+ # it would keep retrying without checking for account_inactive or such, we want to restart via service which will disable an inactive team
16
+ logger.info "#{team.name}: socket closed, restarting ..."
17
+ SlackRubyBotServer::Service.instance.restart! team, self, wait
18
+ client.owner = team
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,81 @@
1
+ module SlackRubyBotServer
2
+ class Service
3
+ include SlackRubyBot::Loggable
4
+
5
+ def self.start!
6
+ Thread.new do
7
+ Thread.current.abort_on_exception = true
8
+ instance.start_from_database!
9
+ end
10
+ end
11
+
12
+ def self.instance
13
+ @instance ||= new
14
+ end
15
+
16
+ def initialize
17
+ @lock = Mutex.new
18
+ @services = {}
19
+ end
20
+
21
+ def start!(team)
22
+ fail 'Token already known.' if @services.key?(team.token)
23
+ logger.info "Starting team #{team}."
24
+ server = SlackRubyBotServer::Server.new(team: team)
25
+ @lock.synchronize do
26
+ @services[team.token] = server
27
+ end
28
+ restart!(team, server)
29
+ rescue StandardError => e
30
+ logger.error e
31
+ end
32
+
33
+ def stop!(team)
34
+ @lock.synchronize do
35
+ fail 'Token unknown.' unless @services.key?(team.token)
36
+ logger.info "Stopping team #{team}."
37
+ @services[team.token].stop!
38
+ @services.delete(team.token)
39
+ end
40
+ rescue StandardError => e
41
+ logger.error e
42
+ end
43
+
44
+ def start_from_database!
45
+ Team.active.each do |team|
46
+ start!(team)
47
+ end
48
+ end
49
+
50
+ def restart!(team, server, wait = 1)
51
+ server.start_async
52
+ rescue StandardError => e
53
+ case e.message
54
+ when 'account_inactive', 'invalid_auth' then
55
+ logger.error "#{team.name}: #{e.message}, team will be deactivated."
56
+ deactivate!(team)
57
+ else
58
+ logger.error "#{team.name}: #{e.message}, restarting in #{wait} second(s)."
59
+ sleep(wait)
60
+ restart! team, server, [wait * 2, 60].min
61
+ end
62
+ end
63
+
64
+ def deactivate!(team)
65
+ team.deactivate!
66
+ @lock.synchronize do
67
+ @services.delete(team.token)
68
+ end
69
+ rescue Mongoid::Errors::Validations => e
70
+ logger.error "#{team.name}: #{e.message}, error - #{e.document.class}, #{e.document.errors.to_hash}, ignored."
71
+ rescue StandardError => e
72
+ logger.error "#{team.name}: #{e.class}, #{e.message}, ignored."
73
+ end
74
+
75
+ def reset!
76
+ @services.values.to_a.each do |server|
77
+ stop!(server.team)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,3 @@
1
+ module SlackRubyBotServer
2
+ VERSION = '0.1.0'
3
+ end
Binary file
Binary file
@@ -0,0 +1,28 @@
1
+ <html>
2
+ <head>
3
+ <title>Slack Ruby Bot Server</title>
4
+ <script src="/scripts/jquery-1.7.1.min.js"></script>
5
+ <script src="/scripts/url.min.js"></script>
6
+ <script src="/scripts/register.js"></script>
7
+ <script src="/scripts/stats.js"></script>
8
+ </head>
9
+ <body style="text-align: center">
10
+ <p>
11
+ <img src='img/slack.png' width="120px">
12
+ </p>
13
+ <p>
14
+ <h3>An opinionated boilerplate for a Slack bot service.</h3>
15
+ </p>
16
+ <p id='messages' />
17
+ <p id='register'>
18
+ <a href="https://slack.com/oauth/authorize?scope=bot&client_id=<%= ENV['SLACK_CLIENT_ID'] %>"><img alt="Add to Slack" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x"></a>
19
+ </p>
20
+ <p id='active_teams_count'>&nbsp;</p>
21
+ <p>
22
+ <small>
23
+ made by <a href="https://twitter.com/dblockdotorg" target="_blank">@dblockdotorg</a>, <a href="https://github.com/dblock/slack-ruby-bot-server" target="_blank">fork me on github</a>
24
+ </small>
25
+ </p>
26
+ </body>
27
+ </html>
28
+