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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +45 -0
- data/.travis.yml +9 -0
- data/CHANGELOG.md +16 -0
- data/CONTRIBUTING.md +125 -0
- data/DEBUGGING.md +25 -0
- data/Gemfile +3 -0
- data/Guardfile +8 -0
- data/LICENSE +21 -0
- data/README.md +49 -0
- data/RELEASING.md +69 -0
- data/Rakefile +16 -0
- data/app.json +9 -0
- data/config/initializers/bson/object_id.rb +12 -0
- data/config/initializers/grape/sort_extension.rb +8 -0
- data/config/initializers/slack-ruby-bot/client.rb +17 -0
- data/config/initializers/slack-ruby-bot/commands/base.rb +21 -0
- data/images/new.png +0 -0
- data/images/slackbotserver.gif +0 -0
- data/images/slackbutton.gif +0 -0
- data/lib/slack-ruby-bot-server.rb +17 -0
- data/lib/slack-ruby-bot-server/api.rb +8 -0
- data/lib/slack-ruby-bot-server/api/endpoints.rb +3 -0
- data/lib/slack-ruby-bot-server/api/endpoints/root_endpoint.rb +22 -0
- data/lib/slack-ruby-bot-server/api/endpoints/status_endpoint.rb +16 -0
- data/lib/slack-ruby-bot-server/api/endpoints/teams_endpoint.rb +70 -0
- data/lib/slack-ruby-bot-server/api/helpers.rb +4 -0
- data/lib/slack-ruby-bot-server/api/helpers/cursor_helpers.rb +37 -0
- data/lib/slack-ruby-bot-server/api/helpers/error_helpers.rb +52 -0
- data/lib/slack-ruby-bot-server/api/helpers/pagination_parameters.rb +19 -0
- data/lib/slack-ruby-bot-server/api/helpers/sort_helpers.rb +53 -0
- data/lib/slack-ruby-bot-server/api/middleware.rb +44 -0
- data/lib/slack-ruby-bot-server/api/presenters.rb +9 -0
- data/lib/slack-ruby-bot-server/api/presenters/paginated_presenter.rb +38 -0
- data/lib/slack-ruby-bot-server/api/presenters/root_presenter.rb +44 -0
- data/lib/slack-ruby-bot-server/api/presenters/status_presenter.rb +40 -0
- data/lib/slack-ruby-bot-server/api/presenters/team_presenter.rb +24 -0
- data/lib/slack-ruby-bot-server/api/presenters/teams_presenter.rb +14 -0
- data/lib/slack-ruby-bot-server/app.rb +85 -0
- data/lib/slack-ruby-bot-server/info.rb +11 -0
- data/lib/slack-ruby-bot-server/models.rb +1 -0
- data/lib/slack-ruby-bot-server/models/team.rb +66 -0
- data/lib/slack-ruby-bot-server/rspec.rb +5 -0
- data/lib/slack-ruby-bot-server/rspec/fabricators/team.rb +5 -0
- data/lib/slack-ruby-bot-server/server.rb +21 -0
- data/lib/slack-ruby-bot-server/service.rb +81 -0
- data/lib/slack-ruby-bot-server/version.rb +3 -0
- data/public/favicon.ico +0 -0
- data/public/img/slack.png +0 -0
- data/public/index.html.erb +28 -0
- data/public/robots.txt +2 -0
- data/public/scripts/jquery-1.7.1.min.js +4 -0
- data/public/scripts/register.js +50 -0
- data/public/scripts/stats.js +14 -0
- data/public/scripts/url.min.js +1 -0
- data/sample_app/.rspec +3 -0
- data/sample_app/Gemfile +14 -0
- data/sample_app/Procfile +1 -0
- data/sample_app/Rakefile +10 -0
- data/sample_app/commands.rb +2 -0
- data/sample_app/commands/help.rb +19 -0
- data/sample_app/commands/whoami.rb +6 -0
- data/sample_app/config.ru +20 -0
- data/sample_app/config/mongoid.yml +27 -0
- data/sample_app/config/newrelic.yml +217 -0
- data/sample_app/spec/commands/help_spec.rb +14 -0
- data/sample_app/spec/commands/whoami_spec.rb +14 -0
- data/sample_app/spec/spec_helper.rb +25 -0
- data/script/console +9 -0
- data/slack-ruby-bot-server.gemspec +48 -0
- data/tasks/db.rake +44 -0
- metadata +523 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
module SlackRubyBot
|
2
|
+
class Client
|
3
|
+
# keep track of the team that the client is connected to
|
4
|
+
attr_accessor :owner
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
Slack.configure do |config|
|
9
|
+
config.logger = Logger.new(STDOUT)
|
10
|
+
config.logger.level = Logger::WARN
|
11
|
+
end
|
12
|
+
|
13
|
+
SlackRubyBot::Client.logger.level = Logger::WARN
|
14
|
+
|
15
|
+
Slack::RealTime::Client.configure do |config|
|
16
|
+
config.store_class = Slack::RealTime::Stores::Starter
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SlackRubyBot
|
2
|
+
module Commands
|
3
|
+
class Base
|
4
|
+
class << self
|
5
|
+
alias_method :_invoke, :invoke
|
6
|
+
|
7
|
+
def invoke(client, data)
|
8
|
+
_invoke client, data
|
9
|
+
rescue Mongoid::Errors::Validations => e
|
10
|
+
logger.info "#{name.demodulize.upcase}: #{client.owner}, error - #{e.document.class}, #{e.document.errors.to_hash}"
|
11
|
+
client.say(channel: data.channel, text: e.document.errors.first[1], gif: 'error')
|
12
|
+
true
|
13
|
+
rescue StandardError => e
|
14
|
+
logger.info "#{name.demodulize.upcase}: #{client.owner}, #{e.class}: #{e}"
|
15
|
+
client.say(channel: data.channel, text: e.message, gif: 'error')
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/images/new.png
ADDED
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'celluloid/current'
|
2
|
+
require 'kaminari/grape'
|
3
|
+
require 'mongoid-scroll'
|
4
|
+
require 'grape-swagger'
|
5
|
+
require 'slack-ruby-bot'
|
6
|
+
|
7
|
+
Dir[File.expand_path('../config/initializers', __dir__) + '/**/*.rb'].each do |file|
|
8
|
+
require file
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'slack-ruby-bot-server/version'
|
12
|
+
require 'slack-ruby-bot-server/info'
|
13
|
+
require 'slack-ruby-bot-server/models'
|
14
|
+
require 'slack-ruby-bot-server/api'
|
15
|
+
require 'slack-ruby-bot-server/app'
|
16
|
+
require 'slack-ruby-bot-server/server'
|
17
|
+
require 'slack-ruby-bot-server/service'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
module Api
|
3
|
+
module Endpoints
|
4
|
+
class RootEndpoint < Grape::API
|
5
|
+
include Helpers::ErrorHelpers
|
6
|
+
|
7
|
+
prefix 'api'
|
8
|
+
|
9
|
+
format :json
|
10
|
+
formatter :json, Grape::Formatter::Roar
|
11
|
+
get do
|
12
|
+
present self, with: Presenters::RootPresenter
|
13
|
+
end
|
14
|
+
|
15
|
+
mount StatusEndpoint
|
16
|
+
mount TeamsEndpoint
|
17
|
+
|
18
|
+
add_swagger_documentation
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
module Api
|
3
|
+
module Endpoints
|
4
|
+
class StatusEndpoint < Grape::API
|
5
|
+
format :json
|
6
|
+
|
7
|
+
namespace :status do
|
8
|
+
desc 'Get system status.'
|
9
|
+
get do
|
10
|
+
present self, with: Presenters::StatusPresenter
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
module Api
|
3
|
+
module Endpoints
|
4
|
+
class TeamsEndpoint < Grape::API
|
5
|
+
format :json
|
6
|
+
helpers Helpers::CursorHelpers
|
7
|
+
helpers Helpers::SortHelpers
|
8
|
+
helpers Helpers::PaginationParameters
|
9
|
+
|
10
|
+
namespace :teams do
|
11
|
+
desc 'Get a team.'
|
12
|
+
params do
|
13
|
+
requires :id, type: String, desc: 'Team ID.'
|
14
|
+
end
|
15
|
+
get ':id' do
|
16
|
+
team = Team.find(params[:id]) || error!('Not Found', 404)
|
17
|
+
present team, with: Presenters::TeamPresenter
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'Get all the teams.'
|
21
|
+
params do
|
22
|
+
optional :active, type: Boolean, desc: 'Return active teams only.'
|
23
|
+
use :pagination
|
24
|
+
end
|
25
|
+
sort Team::SORT_ORDERS
|
26
|
+
get do
|
27
|
+
teams = Team.all
|
28
|
+
teams = teams.active if params[:active]
|
29
|
+
teams = paginate_and_sort_by_cursor(teams, default_sort_order: '-_id')
|
30
|
+
present teams, with: Presenters::TeamsPresenter
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'Create a team using an OAuth token.'
|
34
|
+
params do
|
35
|
+
requires :code, type: String
|
36
|
+
end
|
37
|
+
post do
|
38
|
+
client = Slack::Web::Client.new
|
39
|
+
|
40
|
+
fail 'Missing SLACK_CLIENT_ID or SLACK_CLIENT_SECRET.' unless ENV.key?('SLACK_CLIENT_ID') && ENV.key?('SLACK_CLIENT_SECRET')
|
41
|
+
|
42
|
+
rc = client.oauth_access(
|
43
|
+
client_id: ENV['SLACK_CLIENT_ID'],
|
44
|
+
client_secret: ENV['SLACK_CLIENT_SECRET'],
|
45
|
+
code: params[:code]
|
46
|
+
)
|
47
|
+
|
48
|
+
token = rc['bot']['bot_access_token']
|
49
|
+
team = Team.where(token: token).first
|
50
|
+
team ||= Team.where(team_id: rc['team_id']).first
|
51
|
+
if team && !team.active?
|
52
|
+
team.activate!(token)
|
53
|
+
elsif team
|
54
|
+
fail "Team #{team.name} is already registered."
|
55
|
+
else
|
56
|
+
team = Team.create!(
|
57
|
+
token: token,
|
58
|
+
team_id: rc['team_id'],
|
59
|
+
name: rc['team_name']
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
Service.instance.start!(team)
|
64
|
+
present team, with: Presenters::TeamPresenter
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
module Api
|
3
|
+
module Helpers
|
4
|
+
module CursorHelpers
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# apply cursor-based pagination to a collection
|
8
|
+
# returns a hash:
|
9
|
+
# results: (paginated collection subset)
|
10
|
+
# next: (cursor to the next page)
|
11
|
+
def paginate_by_cursor(coll, &_block)
|
12
|
+
fail 'Both cursor and offset parameters are present, these are mutually exclusive.' if params.key?(:offset) && params.key?(:cursor)
|
13
|
+
results = { results: [], next: nil }
|
14
|
+
size = (params[:size] || 10).to_i
|
15
|
+
if params.key?(:offset)
|
16
|
+
skip = params[:offset].to_i
|
17
|
+
coll = coll.skip(skip)
|
18
|
+
end
|
19
|
+
# some items may be skipped with a block
|
20
|
+
query = block_given? ? coll : coll.limit(size)
|
21
|
+
query.scroll(params[:cursor]) do |record, next_cursor|
|
22
|
+
record = yield(record) if block_given?
|
23
|
+
results[:results] << record if record
|
24
|
+
results[:next] = next_cursor.to_s
|
25
|
+
break if results[:results].count >= size
|
26
|
+
end
|
27
|
+
results[:total_count] = coll.count if params[:total_count] && coll.respond_to?(:count)
|
28
|
+
results
|
29
|
+
end
|
30
|
+
|
31
|
+
def paginate_and_sort_by_cursor(coll, options = {}, &block)
|
32
|
+
Hashie::Mash.new(paginate_by_cursor(sort(coll, options), &block))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
module Api
|
3
|
+
module Helpers
|
4
|
+
module ErrorHelpers
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
rescue_from :all, backtrace: true do |e|
|
9
|
+
backtrace = e.backtrace[0..5].join("\n ")
|
10
|
+
Middleware.logger.error "#{e.class.name}: #{e.message}\n #{backtrace}"
|
11
|
+
error = { type: 'other_error', message: e.message }
|
12
|
+
error[:backtrace] = backtrace
|
13
|
+
rack_response(error.to_json, 400)
|
14
|
+
end
|
15
|
+
# rescue document validation errors into detail json
|
16
|
+
rescue_from Mongoid::Errors::Validations do |e|
|
17
|
+
backtrace = e.backtrace[0..5].join("\n ")
|
18
|
+
Middleware.logger.warn "#{e.class.name}: #{e.message}\n #{backtrace}"
|
19
|
+
rack_response({
|
20
|
+
type: 'param_error',
|
21
|
+
message: e.document.errors.full_messages.uniq.join(', ') + '.',
|
22
|
+
detail: e.document.errors.messages.each_with_object({}) do |(k, v), h|
|
23
|
+
h[k] = v.uniq
|
24
|
+
end
|
25
|
+
}.to_json, 400)
|
26
|
+
end
|
27
|
+
rescue_from Grape::Exceptions::Validation do |e|
|
28
|
+
backtrace = e.backtrace[0..5].join("\n ")
|
29
|
+
Middleware.logger.warn "#{e.class.name}: #{e.message}\n #{backtrace}"
|
30
|
+
rack_response({
|
31
|
+
type: 'param_error',
|
32
|
+
message: 'Invalid parameters.',
|
33
|
+
detail: { e.params.join(', ') => [e.message] }
|
34
|
+
}.to_json, 400)
|
35
|
+
end
|
36
|
+
rescue_from Grape::Exceptions::ValidationErrors do |e|
|
37
|
+
backtrace = e.backtrace[0..5].join("\n ")
|
38
|
+
Middleware.logger.warn "#{e.class.name}: #{e.message}\n #{backtrace}"
|
39
|
+
rack_response({
|
40
|
+
type: 'param_error',
|
41
|
+
message: 'Invalid parameters.',
|
42
|
+
detail: e.errors.each_with_object({}) do |(k, v), h|
|
43
|
+
# JSON does not permit having a key of type Array
|
44
|
+
h[k.count == 1 ? k.first : k.join(', ')] = v
|
45
|
+
end
|
46
|
+
}.to_json, 400)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
module Api
|
3
|
+
module Helpers
|
4
|
+
module PaginationParameters
|
5
|
+
extend Grape::API::Helpers
|
6
|
+
|
7
|
+
params :pagination do
|
8
|
+
optional :offset, type: Integer, desc: 'Offset from which to retrieve.'
|
9
|
+
optional :size, type: Integer, desc: 'Number of items to retrieve for this page or from the current offset.'
|
10
|
+
optional :cursor, type: String, desc: 'Cursor for pagination.'
|
11
|
+
optional :total_count, desc: 'Include total count in the response.'
|
12
|
+
mutually_exclusive :offset, :cursor
|
13
|
+
end
|
14
|
+
|
15
|
+
ALL = %w(cursor size sort offset total_count)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
module Api
|
3
|
+
module Helpers
|
4
|
+
module SortHelpers
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def sort_order(options = {})
|
8
|
+
params[:sort] = options[:default_sort_order] unless params[:sort]
|
9
|
+
return [] unless params[:sort]
|
10
|
+
sort_order = params[:sort].to_s
|
11
|
+
unless options[:default_sort_order] == sort_order
|
12
|
+
supported_sort_orders = route_sort
|
13
|
+
error!("This API doesn't support sorting", 400) if supported_sort_orders.blank?
|
14
|
+
unless supported_sort_orders.include?(sort_order)
|
15
|
+
error!("Invalid sort order: #{sort_order}, must be#{supported_sort_orders.count == 1 ? '' : ' one of'} '#{supported_sort_orders.join("', '")}'", 400)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
sort_order = sort_order.split(',').map do |sort_entry|
|
19
|
+
sort_order = {}
|
20
|
+
if sort_entry[0] == '-'
|
21
|
+
sort_order[:direction] = :desc
|
22
|
+
sort_order[:column] = sort_entry[1..-1]
|
23
|
+
else
|
24
|
+
sort_order[:direction] = :asc
|
25
|
+
sort_order[:column] = sort_entry
|
26
|
+
end
|
27
|
+
error!("Invalid sort: #{sort_entry}", 400) if sort_order[:column].blank?
|
28
|
+
sort_order
|
29
|
+
end
|
30
|
+
sort_order
|
31
|
+
end
|
32
|
+
|
33
|
+
def route_sort
|
34
|
+
(env['api.endpoint'].route_setting(:sort) || {})[:sort]
|
35
|
+
end
|
36
|
+
|
37
|
+
def sort(coll, options = {})
|
38
|
+
sort_order = sort_order(options)
|
39
|
+
unless sort_order.empty?
|
40
|
+
if coll.respond_to?(:asc) && coll.respond_to?(:desc)
|
41
|
+
sort_order.each do |s|
|
42
|
+
coll = coll.send(s[:direction], s[:column])
|
43
|
+
end
|
44
|
+
else
|
45
|
+
error!("Cannot sort #{coll.class.name}", 500)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
coll = coll.is_a?(Module) && coll.respond_to?(:all) ? coll.all : coll
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
%w(rack/cors rack-rewrite rack-server-pages).each { |l| require l }
|
2
|
+
|
3
|
+
module SlackRubyBotServer
|
4
|
+
module Api
|
5
|
+
class Middleware
|
6
|
+
def self.logger
|
7
|
+
@logger ||= begin
|
8
|
+
$stdout.sync = true
|
9
|
+
Logger.new(STDOUT)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.instance
|
14
|
+
@instance ||= Rack::Builder.new do
|
15
|
+
use Rack::Cors do
|
16
|
+
allow do
|
17
|
+
origins '*'
|
18
|
+
resource '*', headers: :any, methods: [:get, :post]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# rewrite HAL links to make them clickable in a browser
|
23
|
+
use Rack::Rewrite do
|
24
|
+
r302 %r{(\/[\w\/]*\/)(%7B|\{)?(.*)(%7D|\})}, '$1'
|
25
|
+
end
|
26
|
+
|
27
|
+
use Rack::ServerPages do |config|
|
28
|
+
config.view_path = [
|
29
|
+
'views', # relative to Dir.pwd
|
30
|
+
'public', # relative to Dir.pwd
|
31
|
+
File.expand_path(File.join(__dir__, '../../../public')) # built-in fallback
|
32
|
+
]
|
33
|
+
end
|
34
|
+
|
35
|
+
run Middleware.new
|
36
|
+
end.to_app
|
37
|
+
end
|
38
|
+
|
39
|
+
def call(env)
|
40
|
+
Endpoints::RootEndpoint.call(env)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'roar/representer'
|
2
|
+
require 'roar/json'
|
3
|
+
require 'roar/json/hal'
|
4
|
+
|
5
|
+
require 'slack-ruby-bot-server/api/presenters/paginated_presenter'
|
6
|
+
require 'slack-ruby-bot-server/api/presenters/status_presenter'
|
7
|
+
require 'slack-ruby-bot-server/api/presenters/team_presenter'
|
8
|
+
require 'slack-ruby-bot-server/api/presenters/teams_presenter'
|
9
|
+
require 'slack-ruby-bot-server/api/presenters/root_presenter'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module SlackRubyBotServer
|
2
|
+
module Api
|
3
|
+
module Presenters
|
4
|
+
module PaginatedPresenter
|
5
|
+
include Roar::JSON::HAL
|
6
|
+
include Roar::Hypermedia
|
7
|
+
include Grape::Roar::Representer
|
8
|
+
|
9
|
+
property :total_count
|
10
|
+
|
11
|
+
link :self do |opts|
|
12
|
+
"#{request_url(opts)}#{query_string_for_cursor(nil, opts)}"
|
13
|
+
end
|
14
|
+
|
15
|
+
link :next do |opts|
|
16
|
+
"#{request_url(opts)}#{query_string_for_cursor(represented.next, opts)}" if represented.next
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def request_url(opts)
|
22
|
+
request = Grape::Request.new(opts[:env])
|
23
|
+
"#{request.base_url}#{opts[:env]['PATH_INFO']}"
|
24
|
+
end
|
25
|
+
|
26
|
+
# replace the page and offset parameters in the query string
|
27
|
+
def query_string_for_cursor(cursor, opts)
|
28
|
+
qs = Hashie::Mash.new(Rack::Utils.parse_nested_query(opts[:env]['QUERY_STRING']))
|
29
|
+
if cursor
|
30
|
+
qs.merge!(cursor: cursor)
|
31
|
+
qs.delete(:offset)
|
32
|
+
end
|
33
|
+
"?#{qs.to_query}" unless qs.empty?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|