slack-ruby-bot-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+