first 2 slack events

This commit is contained in:
Connor Turland 2015-09-30 22:30:53 -04:00
parent c0d29e54bd
commit 0a5b62871c
23 changed files with 427 additions and 39 deletions

View file

@ -15,6 +15,11 @@ gem 'best_in_place' #in-place editing
gem 'kaminari' # pagination gem 'kaminari' # pagination
gem 'uservoice-ruby' gem 'uservoice-ruby'
gem 'dotenv' gem 'dotenv'
gem 'httparty'
gem 'delayed_job', '~> 4.0.2'
gem 'delayed_job_active_record', '~> 4.0.1'
gem 'sequenced', '~> 2.0.0'
gem 'active_model_serializers', '~> 0.8.1'
gem 'paperclip' gem 'paperclip'
gem 'aws-sdk' gem 'aws-sdk'

View file

@ -20,6 +20,8 @@ GEM
erubis (~> 2.7.0) erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
active_model_serializers (0.8.3)
activemodel (>= 3.0)
activejob (4.2.4) activejob (4.2.4)
activesupport (= 4.2.4) activesupport (= 4.2.4)
globalid (>= 0.3.0) globalid (>= 0.3.0)
@ -65,6 +67,11 @@ GEM
coffee-script-source coffee-script-source
execjs execjs
coffee-script-source (1.9.1.1) coffee-script-source (1.9.1.1)
delayed_job (4.0.6)
activesupport (>= 3.0, < 5.0)
delayed_job_active_record (4.0.3)
activerecord (>= 3.0, < 5.0)
delayed_job (>= 3.0, < 4.1)
devise (3.5.2) devise (3.5.2)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
@ -82,6 +89,9 @@ GEM
rails (> 3.0.0) rails (> 3.0.0)
globalid (0.3.6) globalid (0.3.6)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
httparty (0.13.7)
json (~> 1.8)
multi_xml (>= 0.5.2)
i18n (0.7.0) i18n (0.7.0)
jbuilder (2.3.1) jbuilder (2.3.1)
activesupport (>= 3.0.0, < 5) activesupport (>= 3.0.0, < 5)
@ -108,6 +118,7 @@ GEM
mini_portile (0.6.2) mini_portile (0.6.2)
minitest (5.8.0) minitest (5.8.0)
multi_json (1.11.2) multi_json (1.11.2)
multi_xml (0.5.5)
nokogiri (1.6.6.2) nokogiri (1.6.6.2)
mini_portile (~> 0.6.0) mini_portile (~> 0.6.0)
oauth (0.4.7) oauth (0.4.7)
@ -172,6 +183,9 @@ GEM
sprockets (>= 2.8, < 4.0) sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0) sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3) tilt (>= 1.1, < 3)
sequenced (2.0.0)
activerecord (>= 3.0)
activesupport (>= 3.0)
slop (3.6.0) slop (3.6.0)
sprockets (3.3.4) sprockets (3.3.4)
rack (~> 1.0) rack (~> 1.0)
@ -198,15 +212,19 @@ PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
active_model_serializers (~> 0.8.1)
aws-sdk aws-sdk
best_in_place best_in_place
better_errors better_errors
cancancan cancancan
coffee-rails coffee-rails
delayed_job (~> 4.0.2)
delayed_job_active_record (~> 4.0.1)
devise devise
dotenv dotenv
formtastic formtastic
formula formula
httparty
jbuilder jbuilder
jquery-rails jquery-rails
jquery-ui-rails jquery-ui-rails
@ -221,5 +239,9 @@ DEPENDENCIES
rails_12factor rails_12factor
redis redis
sass-rails sass-rails
sequenced (~> 2.0.0)
uglifier uglifier
uservoice-ruby uservoice-ruby
BUNDLED WITH
1.10.6

View file

@ -1 +1,2 @@
web: bundle exec rails server -p $PORT web: bundle exec rails server -p $PORT

1
Vagrantfile vendored
View file

@ -16,7 +16,6 @@ sudo apt-get install nodejs -y
sudo apt-get install npm -y sudo apt-get install npm -y
sudo apt-get install postgresql -y sudo apt-get install postgresql -y
sudo apt-get install libpq-dev -y sudo apt-get install libpq-dev -y
sudo apt-get install redis-server -y
# get imagemagick # get imagemagick
sudo apt-get install imagemagick --fix-missing sudo apt-get install imagemagick --fix-missing

View file

@ -15,10 +15,10 @@ class MappingsController < ApplicationController
def create def create
@mapping = Mapping.new(mapping_params) @mapping = Mapping.new(mapping_params)
@mapping.map.touch(:updated_at)
if @mapping.save if @mapping.save
render json: @mapping, status: :created render json: @mapping, status: :created
@mapping.map.touch(:updated_at)
Events::NewMapping.publish!(@mapping, current_user)
else else
render json: @mapping.errors, status: :unprocessable_entity render json: @mapping.errors, status: :unprocessable_entity
end end

View file

@ -0,0 +1,10 @@
module Routing
extend ActiveSupport::Concern
include Rails.application.routes.url_helpers
included do
def default_url_options
ActionMailer::Base.default_url_options
end
end
end

34
app/models/event.rb Normal file
View file

@ -0,0 +1,34 @@
class Event < ActiveRecord::Base
KINDS = %w[topic_added_to_map synapse_added_to_map]
#has_many :notifications, dependent: :destroy
belongs_to :eventable, polymorphic: true
belongs_to :map
belongs_to :user
scope :sequenced, -> { where('sequence_id is not null').order('sequence_id asc') }
scope :chronologically, -> { order('created_at asc') }
after_create :notify_webhooks!, if: :map
validates_inclusion_of :kind, :in => KINDS
validates_presence_of :eventable
acts_as_sequenced scope: :map_id, column: :sequence_id, skip: lambda {|e| e.map.nil? || e.map_id.nil? }
#def notify!(user)
# notifications.create!(user: user)
#end
def belongs_to?(this_user)
self.user_id == this_user.id
end
def notify_webhooks!
#group = self.discussion.group
self.map.webhooks.each { |webhook| WebhookService.publish! webhook: webhook, event: self }
#group.webhooks.each { |webhook| WebhookService.publish! webhook: webhook, event: self }
end
handle_asynchronously :notify_webhooks!
end

View file

@ -0,0 +1,18 @@
class Events::NewMapping < Event
#after_create :notify_users!
def self.publish!(mapping, user)
create!(kind: mapping.topic_id ? "topic_added_to_map" : "synapse_added_to_map",
eventable: mapping,
map: mapping.map,
user: user)
end
private
#def notify_users!
# unless comment_vote.user == comment_vote.comment_user
# notify!(comment_vote.comment_user)
# end
#end
end

View file

@ -7,6 +7,9 @@ class Map < ActiveRecord::Base
has_many :topics, through: :topicmappings has_many :topics, through: :topicmappings
has_many :synapses, through: :synapsemappings has_many :synapses, through: :synapsemappings
has_many :webhooks, as: :hookable
has_many :events, -> { includes :user }, as: :eventable, dependent: :destroy
# This method associates the attribute ":image" with a file attachment # This method associates the attribute ":image" with a file attachment
has_attached_file :screenshot, :styles => { has_attached_file :screenshot, :styles => {
:thumb => ['188x126#', :png] :thumb => ['188x126#', :png]

13
app/models/webhook.rb Normal file
View file

@ -0,0 +1,13 @@
class Webhook < ActiveRecord::Base
belongs_to :hookable, polymorphic: true
validates :uri, presence: true
validates :hookable, presence: true
validates_inclusion_of :kind, in: %w[slack]
validates :event_types, length: { minimum: 1 }
def headers
{}
end
end

View file

@ -0,0 +1,73 @@
Webhooks::Slack::Base = Struct.new(:event) do
include Routing
def username
"Metamaps Bot"
end
def icon_url
# we'll host our own image soon I reckon
"https://fbcdn-profile-a.akamaihd.net/hprofile-ak-xap1/v/t1.0-1/p50x50/11537803_694991540606106_5116967442850884451_n.jpg?oh=eeba96f797e9cb12340bfd94df2650f0&oe=56802643&__gda__=1447229045_95708238caee2d950ea43c93b38b071c"
end
def text
"something"
end
def attachments
[{
title: attachment_title,
text: attachment_text,
fields: attachment_fields,
fallback: attachment_fallback
}]
end
alias :read_attribute_for_serialization :send
private
#def motion_vote_field
# {
# title: "Vote on this proposal",
# value: "#{proposal_link(eventable, "yes")} · " +
# "#{proposal_link(eventable, "abstain")} · " +
# "#{proposal_link(eventable, "no")} · " +
# "#{proposal_link(eventable, "block")}"
# }
#end
def view_map_on_metamaps(text = nil)
"<#{map_url(eventable.map)}|#{text || eventable.map.name}>"
end
#def view_discussion_on_loomio(params = {})
# { value: discussion_link(I18n.t(:"webhooks.slack.view_it_on_loomio"), params) }
#end
#def proposal_link(proposal, position = nil)
# discussion_link position || proposal.name, { proposal: proposal.key, position: position }
#end
#def discussion_link(text = nil, params = {})
# "<#{discussion_url(eventable.map, params)}|#{text || eventable.discussion.title}>"
#end
def eventable
@eventable ||= event.eventable
end
def author
@author ||= eventable.author
end
end
#webhooks:
# slack:
# motion_closed: "*%{name}* has closed"
# motion_closing_soon: "*%{name}* has a proposal closing in 24 hours"
# motion_outcome_created: "*%{author}* published an outcome in *%{name}*"
# motion_outcome_updated: "*%{author}* updated the outcome for *%{name}*"
# new_motion: "*%{author}* started a new proposal in *%{name}*"
# view_it_on_loomio: "View it on Loomio"

View file

@ -0,0 +1,26 @@
class Webhooks::Slack::SynapseAddedToMap < Webhooks::Slack::Base
def text
"\"*#{eventable.synapse.topic1.name}* #{eventable.synapse.desc || '->'} *#{eventable.synapse.topic2.name}*\" was added as a connection to the map *#{view_map_on_metamaps()}*"
end
def attachment_fallback
"" #{}"*#{eventable.name}*\n#{eventable.description}\n"
end
def attachment_title
"" #proposal_link(eventable)
end
def attachment_text
"" # "#{eventable.description}\n"
end
def attachment_fields
[{
title: "nothing",
value: "nothing"
}] #[motion_vote_field]
end
end

View file

@ -0,0 +1,26 @@
class Webhooks::Slack::TopicAddedToMap < Webhooks::Slack::Base
def text
"New #{eventable.topic.metacode.name} topic *#{eventable.topic.name}* was added to the map *#{view_map_on_metamaps()}*"
end
def attachment_fallback
"" #{}"*#{eventable.name}*\n#{eventable.description}\n"
end
def attachment_title
"" #proposal_link(eventable)
end
def attachment_text
"" # "#{eventable.description}\n"
end
def attachment_fields
[{
title: "nothing",
value: "nothing"
}] #[motion_vote_field]
end
end

View file

@ -0,0 +1,15 @@
class EventSerializer < ActiveModel::Serializer
embed :ids, include: true
attributes :id, :sequence_id, :kind, :map_id, :created_at
has_one :actor, serializer: UserSerializer, root: 'users'
has_one :map, serializer: MapSerializer
def actor
object.user || object.eventable.try(:user)
end
def map
object.eventable.try(:map) || object.eventable.map
end
end

View file

@ -0,0 +1,17 @@
class MapSerializer < ActiveModel::Serializer
embed :ids, include: true
attributes :id,
:name,
:desc,
:permission
#has_one :author, serializer: UserSerializer, root: 'users'
#has_one :group, serializer: GroupSerializer, root: 'groups'
#has_one :active_proposal, serializer: MotionSerializer, root: 'proposals'
#def author
# object.author
#end
end

View file

@ -0,0 +1,28 @@
class UserSerializer < ActiveModel::Serializer
embed :ids, include: true
attributes :id, :name, :username
#:avatar_initials, :avatar_kind, :avatar_url, :profile_url, :gravatar_md5, :time_zone, :search_fragment, :label
#def label
# username
#end
#def gravatar_md5
# Digest::MD5.hexdigest(object.email.to_s.downcase)
#end
#def include_gravatar_md5?
# object.avatar_kind == 'gravatar'
#end
#def avatar_url
# object.avatar_url :large
#end
#def profile_url
# object.avatar_url :large
#end
end

View file

@ -0,0 +1,3 @@
class WebhookSerializer < ActiveModel::Serializer
attributes :text, :username #, :attachments #, :icon_url
end

View file

@ -0,0 +1,18 @@
class WebhookService
def self.publish!(webhook:, event:)
return false unless webhook.event_types.include? event.kind
HTTParty.post webhook.uri, body: payload_for(webhook, event), headers: webhook.headers
end
private
def self.payload_for(webhook, event)
WebhookSerializer.new(webhook_object_for(webhook, event), root: false).to_json
end
def self.webhook_object_for(webhook, event)
"Webhooks::#{webhook.kind.classify}::#{event.kind.classify}".constantize.new(event)
end
end

5
bin/delayed_job Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env ruby
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment'))
require 'delayed/command'
Delayed::Command.new(ARGV).daemonize

View file

@ -6,6 +6,8 @@ Bundler.require(:default, Rails.env)
module Metamaps module Metamaps
class Application < Rails::Application class Application < Rails::Application
config.active_job.queue_adapter = :delayed_job
# Settings in config/environments/* take precedence over those specified here. # Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers # Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded. # -- all .rb files in that directory are automatically loaded.

View file

@ -0,0 +1,2 @@
Delayed::Worker.max_attempts = 2
Delayed::Worker.delay_jobs = !(Rails.env.test? or Rails.env.development?)

View file

@ -0,0 +1,22 @@
class CreateDelayedJobs < ActiveRecord::Migration
def self.up
create_table :delayed_jobs, force: true do |table|
table.integer :priority, default: 0, null: false # Allows some jobs to jump to the front of the queue
table.integer :attempts, default: 0, null: false # Provides for retries, but still fail eventually.
table.text :handler, null: false # YAML-encoded string of the object that will do work
table.text :last_error # reason for last failure (See Note below)
table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
table.datetime :locked_at # Set when a client is working on this object
table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
table.string :locked_by # Who is working on this object (if locked)
table.string :queue # The name of the queue this job is in
table.timestamps null: true
end
add_index :delayed_jobs, [:priority, :run_at], name: "delayed_jobs_priority"
end
def self.down
drop_table :delayed_jobs
end
end

View file

@ -9,21 +9,57 @@
# from scratch. The latter is a flawed and unsustainable approach (the more migrations # from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues). # you'll amass, the slower it'll run and the greater likelihood for issues).
# #
# It's strongly recommended to check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(:version => 20141121204712) do ActiveRecord::Schema.define(version: 20150930233907) do
create_table "in_metacode_sets", :force => true do |t| # These are extensions that must be enabled in order to support this database
t.integer "metacode_id" enable_extension "plpgsql"
t.integer "metacode_set_id"
t.datetime "created_at", :null => false create_table "delayed_jobs", force: :cascade do |t|
t.datetime "updated_at", :null => false t.integer "priority", default: 0, null: false
t.integer "attempts", default: 0, null: false
t.text "handler", null: false
t.text "last_error"
t.datetime "run_at"
t.datetime "locked_at"
t.datetime "failed_at"
t.string "locked_by"
t.string "queue"
t.datetime "created_at"
t.datetime "updated_at"
end end
add_index "in_metacode_sets", ["metacode_id"], :name => "index_in_metacode_sets_on_metacode_id" add_index "delayed_jobs", ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree
add_index "in_metacode_sets", ["metacode_set_id"], :name => "index_in_metacode_sets_on_metacode_set_id"
create_table "mappings", :force => true do |t| create_table "events", force: :cascade do |t|
t.string "kind", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
t.integer "eventable_id"
t.string "eventable_type", limit: 255
t.integer "user_id"
t.integer "map_id"
t.integer "sequence_id"
end
add_index "events", ["created_at"], name: "index_events_on_created_at", using: :btree
add_index "events", ["map_id", "sequence_id"], name: "index_events_on_map_id_and_sequence_id", unique: true, using: :btree
add_index "events", ["map_id"], name: "index_events_on_map_id", using: :btree
add_index "events", ["eventable_type", "eventable_id"], name: "index_events_on_eventable_type_and_eventable_id", using: :btree
add_index "events", ["sequence_id"], name: "index_events_on_sequence_id", using: :btree
create_table "in_metacode_sets", force: :cascade do |t|
t.integer "metacode_id"
t.integer "metacode_set_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "in_metacode_sets", ["metacode_id"], name: "index_in_metacode_sets_on_metacode_id", using: :btree
add_index "in_metacode_sets", ["metacode_set_id"], name: "index_in_metacode_sets_on_metacode_set_id", using: :btree
create_table "mappings", force: :cascade do |t|
t.text "category" t.text "category"
t.integer "xloc" t.integer "xloc"
t.integer "yloc" t.integer "yloc"
@ -31,18 +67,18 @@ ActiveRecord::Schema.define(:version => 20141121204712) do
t.integer "synapse_id" t.integer "synapse_id"
t.integer "map_id" t.integer "map_id"
t.integer "user_id" t.integer "user_id"
t.datetime "created_at", :null => false t.datetime "created_at", null: false
t.datetime "updated_at", :null => false t.datetime "updated_at", null: false
end end
create_table "maps", :force => true do |t| create_table "maps", force: :cascade do |t|
t.text "name" t.text "name"
t.boolean "arranged" t.boolean "arranged"
t.text "desc" t.text "desc"
t.text "permission" t.text "permission"
t.integer "user_id" t.integer "user_id"
t.datetime "created_at", :null => false t.datetime "created_at", null: false
t.datetime "updated_at", :null => false t.datetime "updated_at", null: false
t.boolean "featured" t.boolean "featured"
t.string "screenshot_file_name" t.string "screenshot_file_name"
t.string "screenshot_content_type" t.string "screenshot_content_type"
@ -50,26 +86,26 @@ ActiveRecord::Schema.define(:version => 20141121204712) do
t.datetime "screenshot_updated_at" t.datetime "screenshot_updated_at"
end end
create_table "metacode_sets", :force => true do |t| create_table "metacode_sets", force: :cascade do |t|
t.string "name" t.string "name"
t.text "desc" t.text "desc"
t.integer "user_id" t.integer "user_id"
t.boolean "mapperContributed" t.boolean "mapperContributed"
t.datetime "created_at", :null => false t.datetime "created_at", null: false
t.datetime "updated_at", :null => false t.datetime "updated_at", null: false
end end
add_index "metacode_sets", ["user_id"], :name => "index_metacode_sets_on_user_id" add_index "metacode_sets", ["user_id"], name: "index_metacode_sets_on_user_id", using: :btree
create_table "metacodes", :force => true do |t| create_table "metacodes", force: :cascade do |t|
t.text "name" t.text "name"
t.string "icon" t.string "icon"
t.datetime "created_at", :null => false t.datetime "created_at", null: false
t.datetime "updated_at", :null => false t.datetime "updated_at", null: false
t.string "color" t.string "color"
end end
create_table "synapses", :force => true do |t| create_table "synapses", force: :cascade do |t|
t.text "desc" t.text "desc"
t.text "category" t.text "category"
t.text "weight" t.text "weight"
@ -77,19 +113,19 @@ ActiveRecord::Schema.define(:version => 20141121204712) do
t.integer "node1_id" t.integer "node1_id"
t.integer "node2_id" t.integer "node2_id"
t.integer "user_id" t.integer "user_id"
t.datetime "created_at", :null => false t.datetime "created_at", null: false
t.datetime "updated_at", :null => false t.datetime "updated_at", null: false
end end
create_table "topics", :force => true do |t| create_table "topics", force: :cascade do |t|
t.text "name" t.text "name"
t.text "desc" t.text "desc"
t.text "link" t.text "link"
t.text "permission" t.text "permission"
t.integer "user_id" t.integer "user_id"
t.integer "metacode_id" t.integer "metacode_id"
t.datetime "created_at", :null => false t.datetime "created_at", null: false
t.datetime "updated_at", :null => false t.datetime "updated_at", null: false
t.string "image_file_name" t.string "image_file_name"
t.string "image_content_type" t.string "image_content_type"
t.integer "image_file_size" t.integer "image_file_size"
@ -100,25 +136,25 @@ ActiveRecord::Schema.define(:version => 20141121204712) do
t.datetime "audio_updated_at" t.datetime "audio_updated_at"
end end
create_table "users", :force => true do |t| create_table "users", force: :cascade do |t|
t.string "name" t.string "name"
t.string "email" t.string "email"
t.text "settings" t.text "settings"
t.string "code", :limit => 8 t.string "code", limit: 8
t.string "joinedwithcode", :limit => 8 t.string "joinedwithcode", limit: 8
t.string "crypted_password" t.string "crypted_password"
t.string "password_salt" t.string "password_salt"
t.string "persistence_token" t.string "persistence_token"
t.string "perishable_token" t.string "perishable_token"
t.datetime "created_at", :null => false t.datetime "created_at", null: false
t.datetime "updated_at", :null => false t.datetime "updated_at", null: false
t.string "encrypted_password", :limit => 128, :default => "" t.string "encrypted_password", limit: 128, default: ""
t.string "remember_token" t.string "remember_token"
t.datetime "remember_created_at" t.datetime "remember_created_at"
t.string "reset_password_token" t.string "reset_password_token"
t.datetime "last_sign_in_at" t.datetime "last_sign_in_at"
t.string "last_sign_in_ip" t.string "last_sign_in_ip"
t.integer "sign_in_count", :default => 0 t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at" t.datetime "current_sign_in_at"
t.string "current_sign_in_ip" t.string "current_sign_in_ip"
t.datetime "reset_password_sent_at" t.datetime "reset_password_sent_at"
@ -130,6 +166,16 @@ ActiveRecord::Schema.define(:version => 20141121204712) do
t.integer "generation" t.integer "generation"
end end
add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
create_table "webhooks", force: :cascade do |t|
t.integer "hookable_id"
t.string "hookable_type"
t.string "kind", null: false
t.string "uri", null: false
t.text "event_types", default: [], array: true
end
add_index "webhooks", ["hookable_type", "hookable_id"], name: "index_webhooks_on_hookable_type_and_hookable_id", using: :btree
end end