├── .ruby-version
├── spec
├── dummy
│ ├── lib
│ │ └── assets
│ │ │ └── .keep
│ ├── public
│ │ ├── favicon.ico
│ │ ├── apple-touch-icon.png
│ │ ├── apple-touch-icon-precomposed.png
│ │ ├── 500.html
│ │ ├── 422.html
│ │ └── 404.html
│ ├── app
│ │ ├── assets
│ │ │ ├── images
│ │ │ │ └── .keep
│ │ │ ├── javascripts
│ │ │ │ ├── channels
│ │ │ │ │ └── .keep
│ │ │ │ ├── cable.js
│ │ │ │ └── application.js
│ │ │ ├── config
│ │ │ │ └── manifest.js
│ │ │ └── stylesheets
│ │ │ │ └── application.css
│ │ ├── models
│ │ │ ├── concerns
│ │ │ │ └── .keep
│ │ │ └── application_record.rb
│ │ ├── controllers
│ │ │ ├── concerns
│ │ │ │ └── .keep
│ │ │ └── application_controller.rb
│ │ ├── views
│ │ │ └── layouts
│ │ │ │ ├── mailer.text.erb
│ │ │ │ ├── mailer.html.erb
│ │ │ │ └── application.html.erb
│ │ ├── helpers
│ │ │ └── application_helper.rb
│ │ ├── jobs
│ │ │ └── application_job.rb
│ │ ├── channels
│ │ │ └── application_cable
│ │ │ │ ├── channel.rb
│ │ │ │ └── connection.rb
│ │ └── mailers
│ │ │ └── application_mailer.rb
│ ├── config
│ │ ├── routes.rb
│ │ ├── spring.rb
│ │ ├── environment.rb
│ │ ├── initializers
│ │ │ ├── mime_types.rb
│ │ │ ├── application_controller_renderer.rb
│ │ │ ├── cookies_serializer.rb
│ │ │ ├── filter_parameter_logging.rb
│ │ │ ├── permissions_policy.rb
│ │ │ ├── wrap_parameters.rb
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── assets.rb
│ │ │ ├── inflections.rb
│ │ │ └── content_security_policy.rb
│ │ ├── boot.rb
│ │ ├── cable.yml
│ │ ├── database.yml
│ │ ├── application.rb
│ │ ├── locales
│ │ │ └── en.yml
│ │ ├── storage.yml
│ │ ├── secrets.yml
│ │ ├── puma.rb
│ │ └── environments
│ │ │ ├── test.rb
│ │ │ ├── development.rb
│ │ │ └── production.rb
│ ├── bin
│ │ ├── rake
│ │ ├── bundle
│ │ ├── rails
│ │ ├── yarn
│ │ ├── update
│ │ └── setup
│ ├── schema
│ │ ├── Schemafile
│ │ ├── tokite
│ │ │ ├── Schemafile
│ │ │ ├── tokite_repositories.schema
│ │ │ ├── tokite_secure_user_tokens.schema
│ │ │ ├── tokite_users.schema
│ │ │ └── tokite_rules.schema
│ │ ├── tokite_repositories.schema
│ │ ├── tokite_secure_user_tokens.schema
│ │ ├── tokite_users.schema
│ │ └── tokite_rules.schema
│ ├── config.ru
│ ├── Rakefile
│ └── dot.env
├── factories
│ └── tokite
│ │ ├── rule_factory.rb
│ │ └── user_factory.rb
├── rails_helper.rb
├── models
│ └── tokite
│ │ ├── hook_spec.rb
│ │ ├── search_query_spec.rb
│ │ └── rule_spec.rb
├── spec_helper.rb
├── requests
│ └── hooks_spec.rb
└── payloads
│ ├── issues.json
│ ├── issue_comment.json
│ ├── pull_request.review_requested.json
│ └── pull_request_review.json
├── app
├── assets
│ ├── images
│ │ └── tokite
│ │ │ └── .keep
│ ├── javascripts
│ │ └── tokite
│ │ │ └── application.js
│ ├── config
│ │ └── tokite_manifest.js
│ └── stylesheets
│ │ └── tokite
│ │ └── application.scss
├── models
│ └── tokite
│ │ ├── concerns
│ │ └── .keep
│ │ ├── secure_user_token.rb
│ │ ├── application_record.rb
│ │ ├── revision.rb
│ │ ├── transfer.rb
│ │ ├── hook_event
│ │ ├── base_event.rb
│ │ ├── issue_comment.rb
│ │ ├── pull_request_review_comment.rb
│ │ ├── issues.rb
│ │ ├── pull_request_review.rb
│ │ └── pull_request.rb
│ │ ├── user.rb
│ │ ├── repository.rb
│ │ ├── rule.rb
│ │ ├── hook.rb
│ │ └── search_query.rb
├── controllers
│ └── tokite
│ │ ├── concerns
│ │ └── .keep
│ │ ├── top_controller.rb
│ │ ├── sha_controller.rb
│ │ ├── sessions_controller.rb
│ │ ├── transfers_controller.rb
│ │ ├── hooks_controller.rb
│ │ ├── application_controller.rb
│ │ ├── users_controller.rb
│ │ ├── rules_controller.rb
│ │ └── repositories_controller.rb
├── views
│ ├── layouts
│ │ └── tokite
│ │ │ ├── mailer.text.erb
│ │ │ ├── mailer.html.erb
│ │ │ └── application.html.haml
│ └── tokite
│ │ ├── rules
│ │ ├── new.html.haml
│ │ ├── edit.html.haml
│ │ ├── index.html.haml
│ │ ├── _rules.html.haml
│ │ └── _form.html.haml
│ │ ├── top
│ │ └── show.html.haml
│ │ ├── sessions
│ │ └── new.html.haml
│ │ ├── users
│ │ ├── show.html.haml
│ │ ├── edit.html.haml
│ │ ├── _form.html.haml
│ │ └── index.html.haml
│ │ ├── transfers
│ │ └── new.html.haml
│ │ └── repositories
│ │ ├── index.html.haml
│ │ └── new.html.haml
├── jobs
│ └── tokite
│ │ ├── application_job.rb
│ │ └── notify_github_hook_event_job.rb
├── mailers
│ └── tokite
│ │ └── application_mailer.rb
└── helpers
│ └── tokite
│ └── application_helper.rb
├── .rspec
├── lib
├── tasks
│ ├── yarn.rake
│ └── tokite.rake
├── tokite
│ ├── version.rb
│ ├── exception_logger.rb
│ └── engine.rb
└── tokite.rb
├── schema
├── Schemafile
├── tokite_repositories.schema
├── tokite_secure_user_tokens.schema
├── tokite_users.schema
└── tokite_rules.schema
├── package.json
├── .gitignore
├── config
├── initializers
│ ├── octokit.rb
│ ├── slack_notifier.rb
│ └── omniauth.rb
└── routes.rb
├── Gemfile
├── yarn.lock
├── bin
├── rake
├── rspec
└── rails
├── Rakefile
├── .github
└── workflows
│ └── test.yml
├── MIT-LICENSE
├── tokite.gemspec
├── CHANGELOG.md
└── README.md
/.ruby-version:
--------------------------------------------------------------------------------
1 | 3.1.3
2 |
--------------------------------------------------------------------------------
/spec/dummy/lib/assets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/images/tokite/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/models/tokite/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/controllers/tokite/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/images/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/channels/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/views/layouts/tokite/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/lib/tasks/yarn.rake:
--------------------------------------------------------------------------------
1 | namespace :tokite do
2 |
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/app/assets/javascripts/tokite/application.js:
--------------------------------------------------------------------------------
1 | //= require rails-ujs
2 |
--------------------------------------------------------------------------------
/lib/tokite/version.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | VERSION = '0.7.1'
3 | end
4 |
--------------------------------------------------------------------------------
/app/views/tokite/rules/new.html.haml:
--------------------------------------------------------------------------------
1 | %h1.title New rule
2 | = render "form"
--------------------------------------------------------------------------------
/app/views/tokite/rules/edit.html.haml:
--------------------------------------------------------------------------------
1 | %h1.title Edit rule
2 | = render "form"
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/views/tokite/top/show.html.haml:
--------------------------------------------------------------------------------
1 | = render "tokite/rules/rules", user: current_user
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | end
3 |
--------------------------------------------------------------------------------
/lib/tokite.rb:
--------------------------------------------------------------------------------
1 | require "tokite/engine"
2 |
3 | module Tokite
4 | # Your code goes here...
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | mount Tokite::Engine => "/"
3 | end
4 |
--------------------------------------------------------------------------------
/app/jobs/tokite/application_job.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class ApplicationJob < ActiveJob::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/schema/Schemafile:
--------------------------------------------------------------------------------
1 | Dir.glob(File.expand_path("../*.schema", __FILE__)).each do |path|
2 | require path
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative "../config/boot"
3 | require "rake"
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/spec/dummy/schema/Schemafile:
--------------------------------------------------------------------------------
1 | Dir.glob(File.expand_path("../*.schema", __FILE__)).each do |path|
2 | require path
3 | end
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tokite",
3 | "private": true,
4 | "dependencies": {
5 | "bulma": "^0.9.4"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/schema/tokite/Schemafile:
--------------------------------------------------------------------------------
1 | Dir.glob(File.expand_path("../*.schema", __FILE__)).each do |path|
2 | require path
3 | end
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle/
2 | log/*.log
3 | pkg/
4 | spec/dummy/log/
5 | spec/dummy/tmp/
6 | spec/dummy/.env
7 | node_modules/
8 | Gemfile.lock
9 |
--------------------------------------------------------------------------------
/app/views/tokite/rules/index.html.haml:
--------------------------------------------------------------------------------
1 | - @users.each do |user|
2 | .box
3 | %h2.title= user.name
4 | = render "rules", user: user
5 |
--------------------------------------------------------------------------------
/app/models/tokite/secure_user_token.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class SecureUserToken < ApplicationRecord
3 | belongs_to :user
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/controllers/tokite/top_controller.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class TopController < ApplicationController
3 | def show
4 | end
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/views/tokite/sessions/new.html.haml:
--------------------------------------------------------------------------------
1 | = form_with url: "/auth/github", method: :post do |form|
2 | = form.submit "Login with GitHub", class: "button"
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/spec/dummy/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/app/models/tokite/application_record.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class ApplicationRecord < ActiveRecord::Base
3 | self.abstract_class = true
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery with: :exception
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/spring.rb:
--------------------------------------------------------------------------------
1 | %w(
2 | .ruby-version
3 | .rbenv-vars
4 | tmp/restart.txt
5 | tmp/caching-dev.txt
6 | ).each { |path| Spring.watch(path) }
7 |
--------------------------------------------------------------------------------
/app/assets/config/tokite_manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images/tokite
2 | //= link_directory ../javascripts/tokite .js
3 | //= link_directory ../stylesheets/tokite .css
4 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../config/application', __dir__)
3 | require_relative "../config/boot"
4 | require "rails/commands"
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative "application"
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/app/mailers/tokite/application_mailer.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class ApplicationMailer < ActionMailer::Base
3 | default from: 'from@example.com'
4 | layout 'mailer'
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 |
2 | //= link_tree ../images
3 | //= link_directory ../javascripts .js
4 | //= link_directory ../stylesheets .css
5 | //= link tokite_manifest.js
6 |
--------------------------------------------------------------------------------
/app/views/tokite/users/show.html.haml:
--------------------------------------------------------------------------------
1 | %h2.title
2 | = @user.name
3 | = link_to "Edit", edit_user_path(@user), class: "button is-primary is-small"
4 |
5 | = render "tokite/rules/rules", user: @user
6 |
--------------------------------------------------------------------------------
/spec/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative "config/environment"
4 |
5 | run Rails.application
6 | Rails.application.load_server
7 |
--------------------------------------------------------------------------------
/config/initializers/octokit.rb:
--------------------------------------------------------------------------------
1 | require "octokit"
2 |
3 | if ENV["GITHUB_HOST"].present?
4 | Octokit.configure do |c|
5 | c.api_endpoint = URI.join(ENV["GITHUB_HOST"], "/api/v3/").to_s
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/app/views/tokite/users/edit.html.haml:
--------------------------------------------------------------------------------
1 | %h1.title Edit user
2 | = render "form"
3 |
4 | - if @user.group_user?
5 | = link_to "Delete user", user_path(@user), method: :delete, data: { confirm: %(Delete "#{@user.name}"?) }
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
2 |
3 | require "bundler/setup" # Set up gems listed in the Gemfile.
4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations.
5 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gemspec
4 |
5 | gem 'bootsnap'
6 | gem 'denv'
7 | gem 'byebug'
8 | gem 'listen'
9 | gem 'pry-byebug'
10 | gem 'pry-rails'
11 | gem 'rspec_junit_formatter'
12 | gem 'webrick'
13 | gem 'zeitwerk'
14 |
--------------------------------------------------------------------------------
/spec/dummy/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: test
6 |
7 | production:
8 | adapter: redis
9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
10 | channel_prefix: dummy_production
11 |
--------------------------------------------------------------------------------
/spec/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative 'config/application'
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/spec/factories/tokite/rule_factory.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :rule, class: Tokite::Rule do
3 | association :user, strategy: :create
4 | sequence(:name) {|n| "name_#{n}" }
5 | query { "query" }
6 | channel { "#general" }
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/app/views/tokite/users/_form.html.haml:
--------------------------------------------------------------------------------
1 | = form_for [@user] do |f|
2 | .field
3 | = f.label :name, class: "label"
4 | %p.control
5 | = f.text_field :name, size: 40, class: "input"
6 | .field
7 | %p.control
8 | = f.submit class: "button is-primary"
9 |
--------------------------------------------------------------------------------
/app/views/tokite/rules/_rules.html.haml:
--------------------------------------------------------------------------------
1 | .content
2 | %h3 Rules
3 | %ul
4 | - user.rules.order(:id).each do |rule|
5 | %li= link_to rule.name, edit_rule_path(rule)
6 |
7 | .content
8 | = link_to "New Rule", new_user_rule_path(user), class: "button is-primary"
9 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ActiveSupport::Reloader.to_prepare do
4 | # ApplicationController.renderer.defaults.merge!(
5 | # http_host: 'example.org',
6 | # https: false
7 | # )
8 | # end
9 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
6 |
--------------------------------------------------------------------------------
/app/views/tokite/transfers/new.html.haml:
--------------------------------------------------------------------------------
1 | %h1.title
2 | Transfer
3 | = link_to @rule.name, edit_rule_path(@rule)
4 |
5 | = form_for [@rule, @transfer] do |f|
6 | = f.collection_select :user_id, @users, :id, :name_with_provider
7 | .field.columns
8 | .column.is-8= f.submit "Transfer", class: "button is-primary"
9 |
--------------------------------------------------------------------------------
/app/views/layouts/tokite/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/models/tokite/revision.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class Revision
3 | REVISION_PATH = Rails.root.join("REVISION")
4 |
5 | def self.take
6 | return ENV["HEROKU_SLUG_COMMIT"] if ENV["HEROKU_SLUG_COMMIT"]
7 | return File.read(REVISION_PATH).chomp if File.exist?(REVISION_PATH)
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [
5 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
6 | ]
7 |
--------------------------------------------------------------------------------
/spec/factories/tokite/user_factory.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :user, class: Tokite::User do
3 | provider { "google_oauth2" }
4 | sequence(:uid) {|n| (n * 100).to_s }
5 | name { "name_#{uid}" }
6 | image_url { "https://lh3.googleusercontent.com/-l5MDH3jtWXc/AAAAAAAAAAI/AAAAAAAAAAA/2wjfVaIkYNY/photo.jpg" }
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 | <%= csrf_meta_tags %>
6 |
7 | <%= stylesheet_link_tag 'application', media: 'all' %>
8 | <%= javascript_include_tag 'application' %>
9 |
10 |
11 |
12 | <%= yield %>
13 |
14 |
15 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | bulma@^0.9.4:
6 | version "0.9.4"
7 | resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.9.4.tgz#0ca8aeb1847a34264768dba26a064c8be72674a1"
8 | integrity sha512-86FlT5+1GrsgKbPLRRY7cGDg8fsJiP/jzTqXXVqiUZZ2aZT8uemEOHlU1CDU+TxklPEZ11HZNNWclRBBecP4CQ==
9 |
--------------------------------------------------------------------------------
/lib/tokite/exception_logger.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class ExceptionLogger
3 | def self.callbacks
4 | @callbacks ||= []
5 | end
6 |
7 | def self.configure(callback)
8 | callbacks << callback
9 | end
10 |
11 | def self.log(e, options = {})
12 | callbacks.each{|callback| callback.call(e, options) }
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/dummy/schema/tokite_repositories.schema:
--------------------------------------------------------------------------------
1 | create_table "tokite_repositories", force: :cascade do |t|
2 | t.string "name", limit: 200, null: false
3 | t.string "url", limit: 200, null: false
4 | t.datetime "created_at", null: false
5 | end
6 |
7 | add_index "tokite_repositories", ["name"], name: "tokite_repositories_uniq_name", unique: true, using: :btree
8 |
--------------------------------------------------------------------------------
/spec/dummy/schema/tokite/tokite_repositories.schema:
--------------------------------------------------------------------------------
1 | create_table "tokite_repositories", force: :cascade do |t|
2 | t.string "name", limit: 200, null: false
3 | t.string "url", limit: 200, null: false
4 | t.datetime "created_at", null: false
5 | end
6 |
7 | add_index "tokite_repositories", ["name"], name: "tokite_repositories_uniq_name", unique: true, using: :btree
8 |
--------------------------------------------------------------------------------
/app/jobs/tokite/notify_github_hook_event_job.rb:
--------------------------------------------------------------------------------
1 | require "tokite/exception_logger"
2 |
3 | module Tokite
4 | class NotifyGithubHookEventJob < ApplicationJob
5 | queue_as :default
6 |
7 | def perform(payload)
8 | Rails.application.config.slack_notifier.ping(payload)
9 | rescue Slack::Notifier::APIError => e
10 | ExceptionLogger.log(e, level: :warn)
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/views/tokite/users/index.html.haml:
--------------------------------------------------------------------------------
1 | %table.table.is-striped
2 | %thead
3 | %tr
4 | %th ID
5 | %th Name
6 | %tbody
7 | - @users.each do |user|
8 | %tr
9 | %td= user.id
10 | %td
11 | = link_to user.name_with_provider, user_path(user)
12 |
13 | = link_to "Create group", users_path, method: :post, data: { confirm: %(Create Group?) }, class: "button is-primary"
14 |
15 |
--------------------------------------------------------------------------------
/lib/tokite/engine.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class Engine < ::Rails::Engine
3 | isolate_namespace Tokite
4 |
5 | config.before_configuration do
6 | require "haml-rails"
7 | require "slack-notifier"
8 | end
9 |
10 | initializer "tokite.config" do
11 | Tokite::Engine.routes.default_url_options = { protocol: "https", host: ENV.fetch("APP_HOST", "example.com") }
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/dummy/dot.env:
--------------------------------------------------------------------------------
1 | GOOGLE_CLIENT_ID=111111111111-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com
2 | GOOGLE_CLIENT_SECRET=xxxxxxxxxxxxxx
3 | GOOGLE_HOSTED_DOMAIN=
4 | SECRET_KEY_BASE=37a0d35d995d13af465c390bc0b013472be45ab344009838e19977bbf50aea1a4a2c706ae7b0d6247f72890885afe8bc850345ff4d00cabd2f61bd349bd35b30
5 | SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXXXXXXXX/YYYYYYYYY/AAAAAAAAAAAAAAAAAAAAAAAA
6 | SLACK_NAME=tokite
7 |
--------------------------------------------------------------------------------
/app/controllers/tokite/sha_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Tokite
4 | class ShaController < ActionController::API
5 |
6 | def show
7 | revision = Revision.take
8 | if revision
9 | status = 200
10 | else
11 | revision = "REVISION_FILE_NOT_FOUND"
12 | status = 404
13 | end
14 | render plain: "#{revision}\n", status: status
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/schema/tokite_repositories.schema:
--------------------------------------------------------------------------------
1 | create_table "tokite_repositories", force: :cascade do |t|
2 | t.string "name", limit: 200, null: false
3 | t.string "url", limit: 200, null: false
4 | t.datetime "created_at", null: false
5 | t.boolean "private", null: false, default: false
6 | end
7 |
8 | add_index "tokite_repositories", ["name"], name: "tokite_repositories_uniq_name", unique: true, using: :btree
9 |
--------------------------------------------------------------------------------
/app/models/tokite/transfer.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class Transfer
3 | include ActiveModel::Model
4 |
5 | attr_accessor :rule_id, :user_id
6 | attr_reader :rule
7 |
8 | def self.create!(attributes)
9 | transfer = new(attributes)
10 | transfer.rule.update!(user_id: transfer.user_id)
11 |
12 | return transfer
13 | end
14 |
15 | def rule
16 | @rule ||= Rule.find(rule_id)
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/schema/tokite_secure_user_tokens.schema:
--------------------------------------------------------------------------------
1 | create_table "tokite_secure_user_tokens", force: :cascade do |t|
2 | t.integer "user_id", null: false
3 | t.string "token", limit: 40, null: false
4 | t.datetime "created_at", null: false
5 | t.datetime "updated_at", null: false
6 | end
7 |
8 | add_index "tokite_secure_user_tokens", ["user_id"], name: "tokite_secure_user_token_uniq_user_id", unique: true, using: :btree
9 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/cable.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command.
3 | //
4 | //= require action_cable
5 | //= require_self
6 | //= require_tree ./channels
7 |
8 | (function() {
9 | this.App || (this.App = {});
10 |
11 | App.cable = ActionCable.createConsumer();
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/spec/dummy/schema/tokite_secure_user_tokens.schema:
--------------------------------------------------------------------------------
1 | create_table "tokite_secure_user_tokens", force: :cascade do |t|
2 | t.integer "user_id", null: false
3 | t.string "token", limit: 40, null: false
4 | t.datetime "created_at", null: false
5 | t.datetime "updated_at", null: false
6 | end
7 |
8 | add_index "tokite_secure_user_tokens", ["user_id"], name: "tokite_secure_user_token_uniq_user_id", unique: true, using: :btree
9 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 | #
4 | # This file was generated by Bundler.
5 | #
6 | # The application 'rake' is installed as part of a gem, and
7 | # this file is here to facilitate running it.
8 | #
9 |
10 | require "pathname"
11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12 | Pathname.new(__FILE__).realpath)
13 |
14 | require "rubygems"
15 | require "bundler/setup"
16 |
17 | load Gem.bin_path("rake", "rake")
18 |
--------------------------------------------------------------------------------
/spec/dummy/schema/tokite/tokite_secure_user_tokens.schema:
--------------------------------------------------------------------------------
1 | create_table "tokite_secure_user_tokens", force: :cascade do |t|
2 | t.integer "user_id", null: false
3 | t.string "token", limit: 40, null: false
4 | t.datetime "created_at", null: false
5 | t.datetime "updated_at", null: false
6 | end
7 |
8 | add_index "tokite_secure_user_tokens", ["user_id"], name: "tokite_secure_user_token_uniq_user_id", unique: true, using: :btree
9 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/permissions_policy.rb:
--------------------------------------------------------------------------------
1 | # Define an application-wide HTTP permissions policy. For further
2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy
3 | #
4 | # Rails.application.config.permissions_policy do |f|
5 | # f.camera :none
6 | # f.gyroscope :none
7 | # f.microphone :none
8 | # f.usb :none
9 | # f.fullscreen :self
10 | # f.payment :self, "https://secure.example.com"
11 | # end
12 |
--------------------------------------------------------------------------------
/app/models/tokite/hook_event/base_event.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | module HookEvent
3 | class BaseEvent
4 | attr_reader :hook_params
5 |
6 | def initialize(hook_params)
7 | @hook_params = hook_params
8 | end
9 |
10 | def slack_payload(rule)
11 | {
12 | channel: rule.channel,
13 | text: slack_text,
14 | attachments: slack_attachments(rule),
15 | }
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/bin/rspec:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 | #
4 | # This file was generated by Bundler.
5 | #
6 | # The application 'rspec' is installed as part of a gem, and
7 | # this file is here to facilitate running it.
8 | #
9 |
10 | require "pathname"
11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12 | Pathname.new(__FILE__).realpath)
13 |
14 | require "rubygems"
15 | require "bundler/setup"
16 |
17 | load Gem.bin_path("rspec-core", "rspec")
18 |
--------------------------------------------------------------------------------
/spec/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | default: &default
2 | adapter: postgresql
3 | encoding: unicode
4 | host: localhost
5 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
6 |
7 | development:
8 | <<: *default
9 | database: tokite_development
10 |
11 | test:
12 | <<: *default
13 | <% if ENV["GITHUB_ACTIONS"] %>
14 | user: postgres
15 | password: postgres_password
16 | <% end %>
17 | database: tokite_test
18 |
19 | production:
20 | <<: *default
21 | url: <%= ENV['DATABASE_URL'] %>
22 |
--------------------------------------------------------------------------------
/app/controllers/tokite/sessions_controller.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class SessionsController < ApplicationController
3 | skip_before_action :require_login
4 |
5 | def new
6 | end
7 |
8 | def create
9 | auth = request.env["omniauth.auth"]
10 | user = User.login!(auth)
11 | session[:user_id] = user.id
12 | redirect_to root_path
13 | end
14 |
15 | def destroy
16 | session[:user_id] = nil
17 | redirect_to root_path
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/app/controllers/tokite/transfers_controller.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class TransfersController < ApplicationController
3 | def new
4 | @transfer = Transfer.new(rule_id: params[:rule_id])
5 | @rule = @transfer.rule
6 | @users = User.where.not(id: @rule.user_id)
7 | end
8 |
9 | def create
10 | @transfer = Transfer.create!(rule_id: params[:rule_id], user_id: params[:transfer][:user_id])
11 | redirect_to user_path(@transfer.rule.user_id)
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/app/controllers/tokite/hooks_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Tokite
4 | class HooksController < ActionController::API
5 | GITHUB_EVENT_HEADER = "X-GitHub-Event"
6 |
7 | def create
8 | logger.debug("Hook triggered: #{github_event}")
9 | Hook.fire!(github_event, request.request_parameters)
10 | render plain: "ok"
11 | end
12 |
13 | private
14 |
15 | def github_event
16 | request.headers[GITHUB_EVENT_HEADER]
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/app/views/tokite/repositories/index.html.haml:
--------------------------------------------------------------------------------
1 | - if current_user_token
2 | = link_to "Watch new repositories", new_repository_path, class: "button is-primary"
3 |
4 | .my-6
5 |
6 | %h1.title Watching repositories
7 |
8 | %table.table.is-bordered.is-hoverable
9 | - @repositories.each do |repo|
10 | %tr
11 | %td= link_to repo.name, repo.url
12 | - if show_admin_menu?
13 | %td= link_to "Unwatch", repo, method: :delete, class: "button is-danger", data: { confirm: "Unwatch repository?" }
14 |
--------------------------------------------------------------------------------
/config/initializers/slack_notifier.rb:
--------------------------------------------------------------------------------
1 | if ENV["SLACK_WEBHOOK_URL"]
2 | webhook_url = ENV["SLACK_WEBHOOK_URL"]
3 | elsif Rails.env.test?
4 | webhook_url = "https://example.com/notify"
5 | end
6 |
7 | if webhook_url
8 | Tokite::Engine.config.slack_notifier = Slack::Notifier.new webhook_url do
9 | if ENV["SLACK_ICON_EMOJI"]
10 | defaults username: ENV.fetch("SLACK_NAME", "tokite"), icon_emoji: ENV["SLACK_ICON_EMOJI"]
11 | else
12 | defaults username: ENV.fetch("SLACK_NAME", "tokite")
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/tokite/application.scss:
--------------------------------------------------------------------------------
1 | @import "bulma/sass/utilities/initial-variables";
2 |
3 | // Bootstrap-like colors
4 | $white: #fff;
5 | $black: #000;
6 | $red: #d9534f;
7 | $orange: #f0ad4e;
8 | $yellow: #ffd500;
9 | $green: #5cb85c;
10 | $blue: #0275d8;
11 | $teal: #5bc0de;
12 | $pink: #ff5b77;
13 | $purple: #613d7c;
14 |
15 | $primary: $blue;
16 | $info: $teal;
17 | $success: $green;
18 | $warning: $orange;
19 | $danger: $red;
20 | $dark: $grey-darker;
21 | $text: $grey-dark;
22 |
23 | @import "bulma/bulma";
24 |
--------------------------------------------------------------------------------
/config/initializers/omniauth.rb:
--------------------------------------------------------------------------------
1 | require "omniauth-github"
2 |
3 | Tokite::Engine.config.middleware.use OmniAuth::Builder do
4 | options = { scope: "repo,write:repo_hook" }
5 | host = ENV["GITHUB_HOST"]
6 | if host.present?
7 | options.merge!(
8 | client_options: {
9 | site: "#{host}/api/v3",
10 | authorize_url: "#{host}/login/oauth/authorize",
11 | token_url: "#{host}/login/oauth/access_token",
12 | }
13 | )
14 | end
15 | provider :github, ENV["GITHUB_CLIENT_ID"], ENV["GITHUB_CLIENT_SECRET"], options
16 | end
17 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails gems
3 | # installed from the root of your application.
4 |
5 | ENGINE_ROOT = File.expand_path('..', __dir__)
6 | ENGINE_PATH = File.expand_path('../lib/tokite/engine', __dir__)
7 | APP_PATH = File.expand_path('../spec/dummy/config/application', __dir__)
8 |
9 | # Set up gems listed in the Gemfile.
10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
11 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
12 |
13 | require 'rails/all'
14 | require 'rails/engine/commands'
15 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code
7 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'".
8 | Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"]
9 |
--------------------------------------------------------------------------------
/spec/dummy/bin/yarn:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_ROOT = File.expand_path('..', __dir__)
3 | Dir.chdir(APP_ROOT) do
4 | yarn = ENV["PATH"].split(File::PATH_SEPARATOR).
5 | select { |dir| File.expand_path(dir) != __dir__ }.
6 | product(["yarn", "yarn.cmd", "yarn.ps1"]).
7 | map { |dir, file| File.expand_path(file, dir) }.
8 | find { |file| File.executable?(file) }
9 |
10 | if yarn
11 | exec yarn, *ARGV
12 | else
13 | $stderr.puts "Yarn executable was not detected in the system."
14 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
15 | exit 1
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/rails_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 | require File.expand_path('../dummy/config/environment', __FILE__)
3 |
4 | abort("The Rails environment is running in production mode!") if Rails.env.production?
5 | require 'spec_helper'
6 | require 'rspec/rails'
7 | require 'factory_bot_rails'
8 |
9 | Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
10 |
11 | RSpec.configure do |config|
12 | config.use_transactional_fixtures = true
13 | config.infer_spec_type_from_file_location!
14 |
15 | config.filter_rails_from_backtrace!
16 |
17 | include Tokite::Engine.routes.url_helpers
18 | end
19 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | begin
2 | require 'bundler/setup'
3 | rescue LoadError
4 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5 | end
6 |
7 | require 'rdoc/task'
8 |
9 | RDoc::Task.new(:rdoc) do |rdoc|
10 | rdoc.rdoc_dir = 'rdoc'
11 | rdoc.title = 'Tokite'
12 | rdoc.options << '--line-numbers'
13 | rdoc.rdoc_files.include('README.md')
14 | rdoc.rdoc_files.include('lib/**/*.rb')
15 | end
16 |
17 | APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18 | load 'rails/tasks/engine.rake'
19 |
20 |
21 | load 'rails/tasks/statistics.rake'
22 |
23 | require 'bundler/gem_tasks'
24 |
--------------------------------------------------------------------------------
/schema/tokite_users.schema:
--------------------------------------------------------------------------------
1 | create_table "tokite_users", force: :cascade do |t|
2 | t.string "provider", limit: 20, null: false
3 | t.string "uid", limit: 40, null: false
4 | t.string "name", limit: 40, null: false
5 | t.string "image_url", limit: 200, null: false
6 | t.datetime "created_at", null: false
7 | t.datetime "updated_at", null: false
8 | end
9 |
10 | add_index "tokite_users", ["provider", "uid"], name: "tokite_user_uniq_provider_uid", unique: true, using: :btree
11 |
--------------------------------------------------------------------------------
/spec/dummy/schema/tokite_users.schema:
--------------------------------------------------------------------------------
1 | create_table "tokite_users", force: :cascade do |t|
2 | t.string "provider", limit: 20, null: false
3 | t.string "uid", limit: 40, null: false
4 | t.string "name", limit: 40, null: false
5 | t.string "image_url", limit: 200, null: false
6 | t.datetime "created_at", null: false
7 | t.datetime "updated_at", null: false
8 | end
9 |
10 | add_index "tokite_users", ["provider", "uid"], name: "tokite_user_uniq_provider_uid", unique: true, using: :btree
11 |
--------------------------------------------------------------------------------
/spec/dummy/schema/tokite/tokite_users.schema:
--------------------------------------------------------------------------------
1 | create_table "tokite_users", force: :cascade do |t|
2 | t.string "provider", limit: 20, null: false
3 | t.string "uid", limit: 40, null: false
4 | t.string "name", limit: 40, null: false
5 | t.string "image_url", limit: 200, null: false
6 | t.datetime "created_at", null: false
7 | t.datetime "updated_at", null: false
8 | end
9 |
10 | add_index "tokite_users", ["provider", "uid"], name: "tokite_user_uniq_provider_uid", unique: true, using: :btree
11 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Tokite::Engine.routes.draw do
2 | root to: "top#show"
3 |
4 | resources :hooks, only: %w(create)
5 | resources :users, only: %w(index show create edit update destroy) do
6 | resources :rules, only: %w(new create edit update destroy), shallow: true do
7 | resource :transfers, only: %w(new create)
8 | end
9 | end
10 | resources :rules, only: %w(index)
11 | resources :repositories, only: %w(index new create destroy)
12 |
13 | get "sign_in", to: "sessions#new", as: "sign_in"
14 | get "auth/github/callback", to: "sessions#create"
15 | delete "sign_out", to: "sessions#destroy", as: "sign_out"
16 |
17 | get "site/sha", to: "sha#show"
18 | end
19 |
--------------------------------------------------------------------------------
/app/views/tokite/repositories/new.html.haml:
--------------------------------------------------------------------------------
1 | - if @repositories.present?
2 | = form_tag repositories_path do |f|
3 | %ul
4 | - @repositories.each do |repo|
5 | %li.field
6 | .control
7 | %label.checkbox
8 | = check_box_tag "names[]", repo.name, false, id: "names_#{sanitize_to_id(repo.name)}", multiple: true
9 | = repo.name
10 | - if repo.private?
11 | %span.tag.is-danger{style: "height: 1.5em"} private
12 | = link_to "@", repo.url
13 |
14 | .my-6
15 |
16 | .field
17 | .control
18 | = submit_tag "Import repositories", class: "button is-primary"
19 | - else
20 | No importable repositories.
21 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Add additional assets to the asset load path.
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 | # Add Yarn node_modules folder to the asset load path.
9 | Rails.application.config.assets.paths << Rails.root.join('../../node_modules')
10 |
11 | # Precompile additional assets.
12 | # application.js, application.css, and all non-JS/CSS in the app/assets
13 | # folder are already added.
14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
15 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require_tree .
14 |
--------------------------------------------------------------------------------
/schema/tokite_rules.schema:
--------------------------------------------------------------------------------
1 | create_table "tokite_rules", force: :cascade do |t|
2 | t.integer "user_id", null: false
3 | t.string "name", limit: 50, null: false
4 | t.string "query", limit: 2000, null: false
5 | t.string "channel", limit: 100, null: false
6 | t.string "icon_emoji", limit: 30, null: false, default: ""
7 | t.string "additional_text", limit: 200, null: false, default: ""
8 | t.datetime "created_at", null: false
9 | t.datetime "updated_at", null: false
10 | t.string "display_name", limit: 60, null: false, default: ""
11 | end
12 |
13 | add_index "tokite_rules", ["user_id", "name"], name: "tokite_rule_uniq_name", unique: true, using: :btree
14 |
--------------------------------------------------------------------------------
/app/controllers/tokite/application_controller.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class ApplicationController < ActionController::Base
3 | protect_from_forgery with: :exception
4 |
5 | helper_method :current_user, :current_user_token
6 |
7 | before_action :require_login
8 |
9 | private
10 |
11 | def current_user
12 | @current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
13 | end
14 |
15 | def current_user_token
16 | current_user&.token
17 | end
18 |
19 | def require_login
20 | return if current_user
21 | redirect_to sign_in_path
22 | end
23 |
24 | def octokit_client
25 | @octokit_client ||= Octokit::Client.new(access_token: current_user_token, auto_paginate: true)
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/spec/dummy/schema/tokite_rules.schema:
--------------------------------------------------------------------------------
1 | create_table "tokite_rules", force: :cascade do |t|
2 | t.integer "user_id", null: false
3 | t.string "name", limit: 50, null: false
4 | t.string "query", limit: 2000, null: false
5 | t.string "channel", limit: 100, null: false
6 | t.string "icon_emoji", limit: 20, null: false, default: ""
7 | t.string "additional_text", limit: 200, null: false, default: ""
8 | t.datetime "created_at", null: false
9 | t.datetime "updated_at", null: false
10 | t.string "display_name", limit: 60, null: false, default: ""
11 | end
12 |
13 | add_index "tokite_rules", ["user_id", "name"], name: "tokite_rule_uniq_name", unique: true, using: :btree
14 |
--------------------------------------------------------------------------------
/spec/dummy/schema/tokite/tokite_rules.schema:
--------------------------------------------------------------------------------
1 | create_table "tokite_rules", force: :cascade do |t|
2 | t.integer "user_id", null: false
3 | t.string "name", limit: 50, null: false
4 | t.string "query", limit: 2000, null: false
5 | t.string "channel", limit: 100, null: false
6 | t.string "icon_emoji", limit: 20, null: false, default: ""
7 | t.string "additional_text", limit: 200, null: false, default: ""
8 | t.datetime "created_at", null: false
9 | t.datetime "updated_at", null: false
10 | t.string "display_name", limit: 60, null: false, default: ""
11 | end
12 |
13 | add_index "tokite_rules", ["user_id", "name"], name: "tokite_rule_uniq_name", unique: true, using: :btree
14 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/spec/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative "boot"
2 |
3 | require "rails/all"
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups)
8 | require "tokite"
9 |
10 | module Dummy
11 | class Application < Rails::Application
12 | # Initialize configuration defaults for originally generated Rails version.
13 | config.load_defaults 6.1
14 |
15 | # Configuration for the application, engines, and railties goes here.
16 | #
17 | # These settings can be overridden in specific environments using the files
18 | # in config/environments, which are processed later.
19 | #
20 | # config.time_zone = "Central Time (US & Canada)"
21 | # config.eager_load_paths << Rails.root.join("extras")
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/spec/dummy/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'fileutils'
3 | include FileUtils
4 |
5 | # path to your application root.
6 | APP_ROOT = File.expand_path('..', __dir__)
7 |
8 | def system!(*args)
9 | system(*args) || abort("\n== Command #{args} failed ==")
10 | end
11 |
12 | chdir APP_ROOT do
13 | # This script is a way to update your development environment automatically.
14 | # Add necessary update steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # Install JavaScript dependencies if using Yarn
21 | # system('bin/yarn')
22 |
23 | puts "\n== Updating database =="
24 | system! 'bin/rails db:migrate'
25 |
26 | puts "\n== Removing old logs and tempfiles =="
27 | system! 'bin/rails log:clear tmp:clear'
28 |
29 | puts "\n== Restarting application server =="
30 | system! 'bin/rails restart'
31 | end
32 |
--------------------------------------------------------------------------------
/app/views/layouts/tokite/application.html.haml:
--------------------------------------------------------------------------------
1 | !!!
2 | %html{lang: :en}
3 | %head
4 | %title Tokite
5 | = csrf_meta_tags
6 |
7 | = stylesheet_link_tag 'tokite/application', media: 'all'
8 | = javascript_include_tag 'tokite/application'
9 |
10 | %body
11 | - if current_user
12 | %nav.navbar.has-shadow
13 | .navbar-menu.is-active
14 | .navbar-start
15 | = nav_list_item "Top", root_path, ["top"]
16 | = nav_list_item "Users", users_path, ["users"]
17 | = nav_list_item "Rules", rules_path, ["rules"]
18 | = nav_list_item "Repositories", repositories_path, ["repositories"]
19 | .navbar-end
20 | %span.navbar-item= current_user.name_with_provider
21 |
22 | %section.section
23 | - if flash[:error]
24 | .notification.is-danger= flash[:error]
25 | - if flash[:info]
26 | .notification.is-success= flash[:info]
27 |
28 | = yield
29 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | test:
11 | runs-on: ubuntu-latest
12 | services:
13 | postgres:
14 | image: postgres
15 | ports:
16 | - 5432:5432
17 | env:
18 | POSTGRES_DB: tokite_test
19 | POSTGRES_PASSWORD: postgres_password
20 | options: >-
21 | --health-cmd pg_isready
22 | --health-interval 10s
23 | --health-timeout 5s
24 | --health-retries 5
25 | steps:
26 | - uses: actions/checkout@v3
27 | - uses: ruby/setup-ruby@v1
28 | with:
29 | ruby-version: .ruby-version
30 | bundler-cache: true
31 | - name: Setup DB
32 | run: |
33 | RAILS_ENV=test bundle exec rails app:tokite:ridgepole:install app:tokite:ridgepole:apply
34 | - name: Run tests
35 | run: bundle exec rspec --profile 10
36 |
--------------------------------------------------------------------------------
/spec/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at https://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/app/models/tokite/hook_event/issue_comment.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | module HookEvent
3 | class IssueComment < BaseEvent
4 | def fields
5 | {
6 | event: "issue_comment",
7 | repo: hook_params[:repository][:full_name],
8 | body: hook_params[:comment][:body],
9 | user: hook_params[:comment][:user][:login],
10 | label: hook_params[:issue][:labels].map { |label| label[:name] },
11 | }
12 | end
13 |
14 | def notify?
15 | %w(created).include?(hook_params[:action])
16 | end
17 |
18 | def slack_text
19 | "[#{hook_params[:repository][:full_name]}] New comment by #{hook_params[:comment][:user][:login]} on issue <#{hook_params[:comment][:html_url]}|##{hook_params[:issue][:number]}: #{hook_params[:issue][:title]}>"
20 | end
21 |
22 | def slack_attachment
23 | {
24 | fallback: hook_params[:comment][:body],
25 | text: hook_params[:comment][:body],
26 | color: "good",
27 | }
28 | end
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/app/controllers/tokite/users_controller.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class UsersController < ApplicationController
3 | def index
4 | @users = User.all.order(:id)
5 | end
6 |
7 | def show
8 | @user = User.find(params[:id])
9 | end
10 |
11 | def create
12 | @user = User.create_group_user!("group name")
13 | flash[:info] = "User created."
14 | redirect_to edit_user_path(@user)
15 | end
16 |
17 | def edit
18 | @user = User.find(params[:id])
19 | end
20 |
21 | def update
22 | @user = User.find(params[:id])
23 | @user.update!(user_params)
24 | flash[:info] = "User updated."
25 | redirect_to user_path(@user)
26 | end
27 |
28 | def destroy
29 | @user = User.find(params[:id])
30 | if @user.group_user?
31 | @user.destroy!
32 | flash[:info] = "User deleted."
33 | redirect_to users_path
34 | else
35 | head 400
36 | end
37 | end
38 |
39 | private
40 |
41 | def user_params
42 | params.require(:user).permit(:name)
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2017 hogelog
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/app/models/tokite/hook_event/pull_request_review_comment.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | module HookEvent
3 | class PullRequestReviewComment < BaseEvent
4 | def fields
5 | {
6 | event: "pull_request_review_comment",
7 | repo: hook_params[:repository][:full_name],
8 | body: hook_params[:comment][:body],
9 | user: hook_params[:comment][:user][:login],
10 | }
11 | end
12 |
13 | def notify?
14 | hook_params[:action] == "created"
15 | end
16 |
17 | def slack_text
18 | nil
19 | end
20 |
21 | def slack_attachment
22 | user = hook_params[:comment][:user][:login]
23 | line = hook_params[:comment][:position]
24 | path = hook_params[:comment][:path]
25 | footer_url = hook_params[:comment][:html_url]
26 | footer_text = "Comment by #{user} on line #{line} of #{path}"
27 | {
28 | fallback: "#{hook_params[:comment][:body]}\n#{footer_text}",
29 | text: hook_params[:comment][:body],
30 | footer: "<#{footer_url}|#{footer_text}>"
31 | }
32 | end
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/app/helpers/tokite/application_helper.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | module ApplicationHelper
3 | def nav_list_item(name, path, controllers)
4 | if controllers.include?(params[:controller])
5 | link_to(name, path, class: "navbar-item is-tab is-active")
6 | else
7 | link_to(name, path, class: "navbar-item is-tab")
8 | end
9 | end
10 |
11 | def form_text_field(form, name, options)
12 | html_class = options[:class].dup
13 | object = form.object
14 | content_tag("div", class: "field") do
15 | form.label(name, class: "label") +
16 | if object.errors[name].present?
17 | errors = object.errors[name]
18 | content_tag("p", class: "control") do
19 | form.text_field name, options.merge(class: "#{html_class} is-danger")
20 | end +
21 | content_tag("p", errors.join("\n"), class: "help is-danger")
22 | else
23 | content_tag("p", class: "control") do
24 | form.text_field name, size: 400, class: html_class
25 | end
26 | end
27 | end
28 | end
29 |
30 | def show_admin_menu?
31 | params[:admin]
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/app/models/tokite/hook_event/issues.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | module HookEvent
3 | class Issues < BaseEvent
4 | def fields
5 | {
6 | event: "issues",
7 | repo: hook_params[:repository][:full_name],
8 | title: hook_params[:issue][:title],
9 | body: hook_params[:issue][:body] || "",
10 | user: hook_params[:issue][:user][:login],
11 | label: hook_params[:issue][:labels].map { |label| label[:name] },
12 | }
13 | end
14 |
15 | def notify?
16 | %w(opened).include?(hook_params[:action])
17 | end
18 |
19 | def slack_text
20 | "[#{hook_params[:repository][:full_name]}] Issue created by <#{hook_params[:issue][:user][:html_url]}|#{hook_params[:issue][:user][:login]}>"
21 | end
22 |
23 | def slack_attachment
24 | {
25 | title: "##{hook_params[:issue][:number]} #{hook_params[:issue][:title]}",
26 | title_link: hook_params[:issue][:html_url],
27 | fallback: "#{hook_params[:issue][:title]}\n#{hook_params[:issue][:body]}",
28 | text: hook_params[:issue][:body] || "",
29 | color: "good",
30 | }
31 | end
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/spec/dummy/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require "fileutils"
3 |
4 | # path to your application root.
5 | APP_ROOT = File.expand_path('..', __dir__)
6 |
7 | def system!(*args)
8 | system(*args) || abort("\n== Command #{args} failed ==")
9 | end
10 |
11 | FileUtils.chdir APP_ROOT do
12 | # This script is a way to set up or update your development environment automatically.
13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome.
14 | # Add necessary setup steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # Install JavaScript dependencies
21 | # system! 'bin/yarn'
22 |
23 | # puts "\n== Copying sample files =="
24 | # unless File.exist?('config/database.yml')
25 | # FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
26 | # end
27 |
28 | puts "\n== Preparing database =="
29 | system! 'bin/rails db:prepare'
30 |
31 | puts "\n== Removing old logs and tempfiles =="
32 | system! 'bin/rails log:clear tmp:clear'
33 |
34 | puts "\n== Restarting application server =="
35 | system! 'bin/rails restart'
36 | end
37 |
--------------------------------------------------------------------------------
/spec/dummy/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket
23 |
24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/app/models/tokite/user.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class User < ApplicationRecord
3 | has_many :rules, dependent: :destroy
4 |
5 | has_one :secure_user_token
6 |
7 | delegate :token, to: :secure_user_token, allow_nil: true
8 |
9 | def self.login!(auth)
10 | user = find_or_initialize_by(
11 | provider: auth[:provider],
12 | uid: auth[:uid],
13 | )
14 | if user.persisted?
15 | user.secure_user_token.update!(token: auth[:credentials][:token])
16 | else
17 | user.assign_attributes(
18 | name: auth[:info][:name],
19 | image_url: auth[:info][:image],
20 | )
21 | user.build_secure_user_token(token: auth[:credentials][:token])
22 | transaction do
23 | user.save!
24 | end
25 | end
26 | user
27 | end
28 |
29 | def self.create_group_user!(name)
30 | uuid = SecureRandom.uuid.tr("-", "")
31 | create!(
32 | provider: "GROUP",
33 | uid: uuid,
34 | image_url: "",
35 | name: name
36 | )
37 | end
38 |
39 | def group_user?
40 | provider == "GROUP"
41 | end
42 |
43 | def name_with_provider
44 | "#{name} (#{provider})"
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/app/models/tokite/repository.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class Repository < ApplicationRecord
3 | def self.hook!(octokit_client, github_repo)
4 | repo_name = github_repo.full_name
5 | existing_hook = find_hook(octokit_client, repo_name)
6 | if existing_hook
7 | octokit_client.edit_hook(repo_name, existing_hook.id, "web", hook_config, hook_options)
8 | else
9 | octokit_client.create_hook(repo_name, "web", hook_config, hook_options)
10 | end
11 | create!(name: repo_name, url: github_repo.html_url)
12 | end
13 |
14 | def self.find_hook(octokit_client, repo_name)
15 | octokit_client.hooks(repo_name).find {|hook| hook.config.url == hooks_url }
16 | end
17 |
18 | def self.hook_config
19 | {
20 | content_type: "json",
21 | url: hooks_url,
22 | }
23 | end
24 |
25 | def self.hook_options
26 | {
27 | events: ["*"]
28 | }
29 | end
30 |
31 | def self.hooks_url
32 | Tokite::Engine.routes.url_helpers.hooks_url
33 | end
34 |
35 | def unhook!(octokit_client)
36 | hook = self.class.find_hook(octokit_client, name)
37 | octokit_client.remove_hook(name, hook.id) if hook
38 | destroy!
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/tokite.gemspec:
--------------------------------------------------------------------------------
1 | $:.push File.expand_path("../lib", __FILE__)
2 |
3 | # Maintain your gem's version:
4 | require "tokite/version"
5 |
6 | # Describe your gem and declare its dependencies:
7 | Gem::Specification.new do |s|
8 | s.name = "tokite"
9 | s.version = Tokite::VERSION
10 | s.authors = ["hogelog"]
11 | s.email = ["konbu.komuro@gmail.com"]
12 | s.homepage = "https://github.com/cookpad/tokite/"
13 | s.summary = "Customizable Slack notification from GitHub"
14 | s.description = "Customizable Slack notification from GitHub"
15 | s.license = "MIT"
16 |
17 | s.files = Dir["{app,config,db,lib,schema}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
18 |
19 | s.add_dependency "rails", "~> 6.1.0"
20 | s.add_dependency "pg"
21 | s.add_dependency 'sass-rails', '~> 6.0'
22 | s.add_dependency "haml"
23 | s.add_dependency "haml-rails"
24 | s.add_dependency "omniauth-github", ">= 2.0.0"
25 | s.add_dependency "omniauth-rails_csrf_protection"
26 | s.add_dependency "octokit"
27 | s.add_dependency "slack-notifier"
28 | s.add_dependency "ridgepole"
29 | s.add_dependency "parslet"
30 |
31 | s.add_development_dependency "rspec-rails", '>= 3.9.0'
32 | s.add_development_dependency "factory_bot_rails"
33 | end
34 |
--------------------------------------------------------------------------------
/spec/models/tokite/hook_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Tokite::Hook, type: :model do
4 | xdescribe "fire!" do
5 | context "for debug" do
6 | before do
7 | FactoryBot.create(:rule, query: "/./", channel: "#test-private", additional_text: "@hogelog Hi!", display_name: "tokite-spec")
8 | end
9 | let(:params) {
10 | JSON.parse(payload_json("#{event}.json")).with_indifferent_access
11 | }
12 |
13 | context "with issue comment" do
14 | let(:event) { "issue_comment" }
15 | it { Tokite::Hook.fire!(event, params) }
16 | end
17 |
18 | context "with issues" do
19 | let(:event) { "issues" }
20 | it { Tokite::Hook.fire!(event, params) }
21 | end
22 |
23 | context "with pull request" do
24 | let(:event) { "pull_request" }
25 | it { Tokite::Hook.fire!(event, params) }
26 | end
27 |
28 | context "with pull request review" do
29 | let(:event) { "pull_request_review" }
30 | it { Tokite::Hook.fire!(event, params) }
31 | end
32 |
33 | context "with pull request review comment" do
34 | let(:event) { "pull_request_review_comment" }
35 | it { Tokite::Hook.fire!(event, params) }
36 | end
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/spec/dummy/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rails secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | # Shared secrets are available across all environments.
14 |
15 | # shared:
16 | # api_key: a1B2c3D4e5F6
17 |
18 | # Environmental secrets are only available for that specific environment.
19 |
20 | development:
21 | secret_key_base: 223270007dfd9884790a106821142eac09c2a56c9e0ba1b82b053ea317438b8ac352e65c9c529737e9cdd4ffb33d5c7acbd6b52245fc64c99a0259ff2a50fc45
22 |
23 | test:
24 | secret_key_base: 8bd1a9cc6ab0bcdb6a6cebb74461859f85249b543de518489e88f82f1e0eccb54ddc4d61c67a67a9a7f467125ec5cd76f04e540883da238cc0f4e12084e82b99
25 |
26 | # Do not keep production secrets in the unencrypted secrets file.
27 | # Instead, either read values from the environment.
28 | # Or, use `bin/rails secrets:setup` to configure encrypted secrets
29 | # and move the `production:` environment over there.
30 |
31 | production:
32 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
33 |
--------------------------------------------------------------------------------
/app/controllers/tokite/rules_controller.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class RulesController < ApplicationController
3 | def index
4 | @users = User.all.includes(:rules)
5 | end
6 |
7 | def new
8 | @rule_user = User.find(params[:user_id])
9 | @rule = @rule_user.rules.build
10 | end
11 |
12 | def create
13 | @rule_user = User.find(params[:user_id])
14 | @rule = @rule_user.rules.new(rule_params)
15 | if @rule.save
16 | flash[:info] = "Rule created."
17 | redirect_to user_path(@rule.user_id)
18 | else
19 | render "new"
20 | end
21 | end
22 |
23 | def edit
24 | @rule_user = nil
25 | @rule = Rule.find(params[:id])
26 | end
27 |
28 | def update
29 | @rule = Rule.find(params[:id])
30 | @rule.assign_attributes(rule_params)
31 | if @rule.save
32 | flash[:info] = "Rule updated."
33 | redirect_to user_path(@rule.user_id)
34 | else
35 | render "edit"
36 | end
37 | end
38 |
39 | def destroy
40 | @rule = Rule.find(params[:id])
41 | @rule.destroy!
42 | flash[:info] = "Rule deleted."
43 | redirect_to user_path(@rule.user_id)
44 | end
45 |
46 | private
47 |
48 | def rule_params
49 | params.require(:rule).permit(:user_id, :name, :query, :channel, :icon_emoji, :additional_text, :display_name)
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy
4 | # For further information see the following documentation
5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
6 |
7 | # Rails.application.config.content_security_policy do |policy|
8 | # policy.default_src :self, :https
9 | # policy.font_src :self, :https, :data
10 | # policy.img_src :self, :https, :data
11 | # policy.object_src :none
12 | # policy.script_src :self, :https
13 | # policy.style_src :self, :https
14 | # # If you are using webpack-dev-server then specify webpack-dev-server host
15 | # policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development?
16 |
17 | # # Specify URI for violation reports
18 | # # policy.report_uri "/csp-violation-report-endpoint"
19 | # end
20 |
21 | # If you are using UJS then enable automatic nonce generation
22 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
23 |
24 | # Set the nonce only to specific directives
25 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
26 |
27 | # Report CSP violations to a specified URI
28 | # For further information see the following documentation:
29 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
30 | # Rails.application.config.content_security_policy_report_only = true
31 |
--------------------------------------------------------------------------------
/lib/tasks/tokite.rake:
--------------------------------------------------------------------------------
1 | namespace :tokite do
2 | namespace :ridgepole do
3 | def engine_path(file)
4 | Tokite::Engine.root.join(file).to_s
5 | end
6 |
7 | def app_path(file)
8 | Rails.root.join(file).to_s
9 | end
10 |
11 | def ridgepole_exec(*args)
12 | yml = Rails.root.join("config/database.yml").to_s
13 | sh "bundle", "exec", "ridgepole", "-c", yml, "-E", Rails.env, *args
14 | end
15 |
16 | desc "Apply Schemafile"
17 | task :apply do
18 | ridgepole_exec("--file", app_path("schema/Schemafile"), "-a")
19 | end
20 |
21 | desc "Apply Schemafile (dry-run)"
22 | task :"dry-run" do
23 | ridgepole_exec("--file", app_path("schema/Schemafile"), "-a", "--dry-run")
24 | end
25 |
26 | desc "Install schema"
27 | task :install do
28 | schema_dir = app_path("schema")
29 | tokite_schema_dir = app_path("schema/tokite")
30 | mkdir_p(tokite_schema_dir) unless Dir.exist?(tokite_schema_dir)
31 |
32 | Dir.glob("#{engine_path("schema")}/**/*.schema").each do |src_path|
33 | basename = File.basename(src_path)
34 | if File.exist?(File.join(tokite_schema_dir, basename))
35 | puts "Skip install schema #{src_path}"
36 | else
37 | puts "Install schema #{src_path}"
38 | cp src_path, tokite_schema_dir
39 | cp src_path, schema_dir
40 | end
41 | end
42 | end
43 | end
44 |
45 | namespace :yarn do
46 | desc "Install yarn packages"
47 | task :install do
48 | sh "yarn", "add", "bulma"
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | RSpec.configure do |config|
2 | # rspec-expectations config goes here. You can use an alternate
3 | # assertion/expectation library such as wrong or the stdlib/minitest
4 | # assertions if you prefer.
5 | config.expect_with :rspec do |expectations|
6 | # This option will default to `true` in RSpec 4. It makes the `description`
7 | # and `failure_message` of custom matchers include text for helper methods
8 | # defined using `chain`, e.g.:
9 | # be_bigger_than(2).and_smaller_than(4).description
10 | # # => "be bigger than 2 and smaller than 4"
11 | # ...rather than:
12 | # # => "be bigger than 2"
13 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true
14 | end
15 |
16 | # rspec-mocks config goes here. You can use an alternate test double
17 | # library (such as bogus or mocha) by changing the `mock_with` option here.
18 | config.mock_with :rspec do |mocks|
19 | # Prevents you from mocking or stubbing a method that does not exist on
20 | # a real object. This is generally recommended, and will default to
21 | # `true` in RSpec 4.
22 | mocks.verify_partial_doubles = true
23 | end
24 |
25 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
26 | # have no way to turn it off -- the option exists only for backwards
27 | # compatibility in RSpec 3). It causes shared context metadata to be
28 | # inherited by the metadata hash of host groups and examples, rather than
29 | # triggering implicit auto-inclusion in groups with matching metadata.
30 | config.shared_context_metadata_behavior = :apply_to_host_groups
31 |
32 | config.filter_run_when_matching :focus
33 | config.disable_monkey_patching!
34 |
35 | def payload_json(path)
36 | File.read(File.join(__dir__, "payloads", path))
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/app/controllers/tokite/repositories_controller.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class RepositoriesController < ApplicationController
3 | before_action :require_github_token, only: [:new, :create, :destroy]
4 |
5 | def index
6 | @repositories = Repository.all
7 | end
8 |
9 | def new
10 | github_repos = octokit_client.repositories.
11 | select{|r| r.permissions.admin }.
12 | delete_if(&:fork).
13 | delete_if(&:archived)
14 | @repositories = github_repos.map do |repo|
15 | Repository.new(name: repo.full_name, url: repo.html_url, private: repo.private)
16 | end
17 | Repository.all.pluck(:name).each do |existing_name|
18 | @repositories.delete_if {|repo| repo.name == existing_name }
19 | end
20 | end
21 |
22 | def create
23 | if params[:names].nil?
24 | flash[:error] = "Error: No repository was selected"
25 | else
26 | github_repos = params[:names].map do |name|
27 | octokit_client.repository(name)
28 | end
29 | errors = github_repos.select(&:archived).map(&:full_name)
30 | if errors != []
31 | flash[:error] = %(Error: The following repositories have been archived: #{errors.join(", ")})
32 | else
33 | github_repos.each do |repo|
34 | Repository.hook!(octokit_client, repo)
35 | end
36 | flash[:info] = "Import repositories."
37 | end
38 | end
39 | redirect_to repositories_path
40 | end
41 |
42 | def destroy
43 | repo = Repository.find(params[:id])
44 | repo.unhook!(octokit_client)
45 | flash[:info] = "Unhook repository #{repo.name}"
46 | redirect_to repositories_path
47 | end
48 |
49 | private
50 |
51 | def require_github_token
52 | redirect_to repositories_path unless current_user_token
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/spec/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/models/tokite/hook_event/pull_request_review.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | module HookEvent
3 | class PullRequestReview < BaseEvent
4 | def fields
5 | {
6 | event: "pull_request_review",
7 | repo: hook_params[:repository][:full_name],
8 | body: hook_params[:review][:body] || "",
9 | user: hook_params[:review][:user][:login],
10 | review_state: hook_params[:review][:state],
11 | }
12 | end
13 |
14 | def notify?
15 | if hook_params[:action] != "submitted"
16 | false
17 | elsif hook_params[:review][:state] == "commented"
18 | hook_params[:review][:body] != nil
19 | else
20 | true
21 | end
22 | end
23 |
24 | def slack_text
25 | repo = "<#{hook_params[:repository][:html_url]}|[#{hook_params[:repository][:full_name]}]>"
26 | user = "<#{hook_params[:review][:user][:html_url]}|#{hook_params[:review][:user][:login]}>"
27 | title = "<#{hook_params[:pull_request][:html_url]}|##{hook_params[:pull_request][:number]} #{hook_params[:pull_request][:title]}>"
28 | case hook_params[:review][:state]
29 | when "commented"
30 | "#{repo} New comment by #{user} on pull request #{title}"
31 | when "approved"
32 | "#{repo} #{user} approved #{title}"
33 | when "changes_requested"
34 | "#{repo} #{user} requested changes #{title}"
35 | end
36 | end
37 |
38 | def slack_attachment
39 | case hook_params[:review][:state]
40 | when "commented"
41 | when "approved"
42 | color = "good"
43 | when "changes_requested"
44 | color = "warning"
45 | end
46 | {
47 | fallback: hook_params[:review][:body] || "",
48 | text: hook_params[:review][:body] || "",
49 | color: color,
50 | }
51 | end
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/app/models/tokite/rule.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class Rule < ApplicationRecord
3 | attr_reader :search_query
4 |
5 | belongs_to :user
6 |
7 | validates :name, presence: true
8 | validate :validate_query
9 | validate :validate_user_id
10 | validate :validate_channel
11 |
12 | before_validation :normalize_channel
13 |
14 | INVALID_CHANNEL_CHARS = [" ", ","]
15 |
16 | # TODO: Performance
17 | def self.matched_rules(event)
18 | Rule.all.to_a.select do |rule|
19 | rule.match?(event)
20 | end
21 | end
22 |
23 | def search_query
24 | @search_query ||= SearchQuery.new(query)
25 | end
26 |
27 | def match?(event)
28 | search_query.match?(event.fields)
29 | end
30 |
31 | def slack_attachment_fallback
32 | "#{name} by #{user.name}"
33 | end
34 |
35 | def slack_attachment_text
36 | "#{rule_name_link} (#{user_link}) "
37 | end
38 |
39 | def rule_name_link
40 | "<#{Tokite::Engine.routes.url_helpers.edit_rule_url(self)}|#{name}>"
41 | end
42 |
43 | def user_link
44 | "<#{Tokite::Engine.routes.url_helpers.user_url(user)}|#{user.name}>"
45 | end
46 |
47 | private
48 |
49 | def validate_query
50 | SearchQuery.validate(query)
51 | rescue SearchQuery::QueryError => e
52 | errors.add(:query, e.message)
53 | end
54 |
55 | def validate_user_id
56 | unless User.find_by(id: user_id)
57 | errors.add(:user_id, "Unknown user_id: #{user_id}")
58 | end
59 | end
60 |
61 | def validate_channel
62 | INVALID_CHANNEL_CHARS.each do |invalid_char|
63 | errors.add(:channel, %(Invalid character: "#{invalid_char}")) if channel.index(invalid_char)
64 | end
65 | end
66 |
67 | def normalize_channel
68 | if channel.start_with?("#")
69 | self.channel = channel.strip
70 | else
71 | self.channel = "##{channel.strip}"
72 | end
73 | end
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/spec/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/spec/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/models/tokite/hook_event/pull_request.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | module HookEvent
3 | class PullRequest < BaseEvent
4 | def fields
5 | {
6 | event: "pull_request",
7 | repo: hook_params[:repository][:full_name],
8 | title: hook_params[:pull_request][:title],
9 | body: hook_params[:pull_request][:body],
10 | user: hook_params[:pull_request][:user][:login],
11 | label: hook_params[:pull_request][:labels].map { |label| label[:name] },
12 | requested_reviewer: hook_params[:requested_reviewer]&.[](:login) || hook_params[:pull_request][:requested_reviewers].map { |reviewer| reviewer[:login] },
13 | requested_team: hook_params[:pull_request][:requested_teams].map { |team| parse_team_name(team) },
14 | }
15 | end
16 |
17 | def parse_team_name(team)
18 | html_url = team[:html_url]
19 | if /\/orgs\/(?[^\s\/]+)\/teams\/(?[^\s\/]+)\z/ =~ html_url
20 | org_name + "/" + team_name
21 | else
22 | team[:slug] || team[:name]
23 | end
24 | end
25 |
26 | def notify?
27 | %w(opened review_requested).include?(hook_params[:action])
28 | end
29 |
30 | def slack_text
31 | case hook_params[:action]
32 | when 'opened'
33 | "[#{hook_params[:repository][:full_name]}] Pull request submitted by <#{hook_params[:pull_request][:user][:html_url]}|#{hook_params[:pull_request][:user][:login]}>"
34 | when 'review_requested'
35 | "[#{hook_params[:repository][:full_name]}] Pull request review requested by <#{hook_params[:pull_request][:user][:html_url]}|#{hook_params[:pull_request][:user][:login]}>"
36 | end
37 | end
38 |
39 | def slack_attachment
40 | {
41 | title: "##{hook_params[:pull_request][:number]} #{hook_params[:pull_request][:title]}",
42 | title_link: hook_params[:pull_request][:html_url],
43 | fallback: "#{hook_params[:pull_request][:title]}\n#{hook_params[:pull_request][:body]}",
44 | text: hook_params[:pull_request][:body] || "No description provided.",
45 | color: "good",
46 | }
47 | end
48 | end
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/app/models/tokite/hook.rb:
--------------------------------------------------------------------------------
1 | module Tokite
2 | class Hook
3 | attr_reader :event
4 |
5 | HOOK_EVENTS = {
6 | "pull_request" => HookEvent::PullRequest,
7 | "issues" => HookEvent::Issues,
8 | "issue_comment" => HookEvent::IssueComment,
9 | "pull_request_review" => HookEvent::PullRequestReview,
10 | "pull_request_review_comment" => HookEvent::PullRequestReviewComment,
11 | }.freeze
12 |
13 | def self.fire!(github_event, hook_params)
14 | event_class = HOOK_EVENTS[github_event]
15 | Hook.new(event_class.new(hook_params)).fire! if event_class
16 | end
17 |
18 | def initialize(event)
19 | @event = event
20 | end
21 |
22 | def fire!
23 | return unless event.notify?
24 | payloads = []
25 | Rule.matched_rules(event).each do |rule|
26 | attachment = event.slack_attachment
27 | attachment[:fallback] += "\n\n#{rule.slack_attachment_fallback}"
28 | attachment[:text] += "\n\n#{rule.slack_attachment_text}"
29 | emoji = rule.icon_emoji.chomp.presence
30 | additional_text = rule.additional_text
31 |
32 | if payloads.none? {|payload| payload[:channel] == rule.channel && payload[:emoji] == emoji && payload[:additional_text] == additional_text }
33 | payloads << {
34 | channel: rule.channel,
35 | username: rule.display_name.presence || "tokite",
36 | text: event.slack_text,
37 | emoji: emoji,
38 | additional_text: additional_text,
39 | attachments: [attachment],
40 | }
41 | end
42 | end
43 | payloads.each do |payload|
44 | notify!(
45 | channel: payload[:channel],
46 | text: payload[:text],
47 | icon_emoji: payload[:emoji],
48 | attachments: payload[:attachments],
49 | username: payload[:username]
50 | )
51 | if payload[:additional_text].present?
52 | notify!(
53 | channel: payload[:channel],
54 | text: payload[:additional_text],
55 | icon_emoji: payload[:emoji],
56 | parse: "full",
57 | username: payload[:username]
58 | )
59 | end
60 | end
61 | end
62 |
63 | def notify!(payload)
64 | NotifyGithubHookEventJob.perform_now(payload.compact)
65 | end
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/spec/dummy/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | threads threads_count, threads_count
9 |
10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
11 | #
12 | port ENV.fetch("PORT") { 3000 }
13 |
14 | # Specifies the `environment` that Puma will run in.
15 | #
16 | environment ENV.fetch("RAILS_ENV") { "development" }
17 |
18 | # Specifies the number of `workers` to boot in clustered mode.
19 | # Workers are forked webserver processes. If using threads and workers together
20 | # the concurrency of the application would be max `threads` * `workers`.
21 | # Workers do not work on JRuby or Windows (both of which do not support
22 | # processes).
23 | #
24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
25 |
26 | # Use the `preload_app!` method when specifying a `workers` number.
27 | # This directive tells Puma to first boot the application and load code
28 | # before forking the application. This takes advantage of Copy On Write
29 | # process behavior so workers use less memory. If you use this option
30 | # you need to make sure to reconnect any threads in the `on_worker_boot`
31 | # block.
32 | #
33 | # preload_app!
34 |
35 | # If you are preloading your application and using Active Record, it's
36 | # recommended that you close any connections to the database before workers
37 | # are forked to prevent connection leakage.
38 | #
39 | # before_fork do
40 | # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
41 | # end
42 |
43 | # The code in the `on_worker_boot` will be called if you are using
44 | # clustered mode by specifying a number of `workers`. After each worker
45 | # process is booted, this block will be run. If you are using the `preload_app!`
46 | # option, you will want to use this block to reconnect to any threads
47 | # or connections that may have been created at application boot, as Ruby
48 | # cannot share connections between processes.
49 | #
50 | # on_worker_boot do
51 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
52 | # end
53 | #
54 |
55 | # Allow puma to be restarted by `rails restart` command.
56 | plugin :tmp_restart
57 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | # The test environment is used exclusively to run your application's
4 | # test suite. You never need to work with it otherwise. Remember that
5 | # your test database is "scratch space" for the test suite and is wiped
6 | # and recreated between test runs. Don't rely on the data there!
7 |
8 | Rails.application.configure do
9 | # Settings specified here will take precedence over those in config/application.rb.
10 |
11 | config.cache_classes = true
12 |
13 | # Do not eager load code on boot. This avoids loading your whole application
14 | # just for the purpose of running a single test. If you are using a tool that
15 | # preloads Rails for running tests, you may have to set it to true.
16 | config.eager_load = false
17 |
18 | # Configure public file server for tests with Cache-Control for performance.
19 | config.public_file_server.enabled = true
20 | config.public_file_server.headers = {
21 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
22 | }
23 |
24 | # Show full error reports and disable caching.
25 | config.consider_all_requests_local = true
26 | config.action_controller.perform_caching = false
27 | config.cache_store = :null_store
28 |
29 | # Raise exceptions instead of rendering exception templates.
30 | config.action_dispatch.show_exceptions = false
31 |
32 | # Disable request forgery protection in test environment.
33 | config.action_controller.allow_forgery_protection = false
34 |
35 | # Store uploaded files on the local file system in a temporary directory.
36 | config.active_storage.service = :test
37 |
38 | config.action_mailer.perform_caching = false
39 |
40 | # Tell Action Mailer not to deliver emails to the real world.
41 | # The :test delivery method accumulates sent emails in the
42 | # ActionMailer::Base.deliveries array.
43 | config.action_mailer.delivery_method = :test
44 |
45 | # Print deprecation notices to the stderr.
46 | config.active_support.deprecation = :stderr
47 |
48 | # Raise exceptions for disallowed deprecations.
49 | config.active_support.disallowed_deprecation = :raise
50 |
51 | # Tell Active Support which deprecation messages to disallow.
52 | config.active_support.disallowed_deprecation_warnings = []
53 |
54 | # Raises error for missing translations.
55 | # config.i18n.raise_on_missing_translations = true
56 |
57 | # Annotate rendered view with file names.
58 | # config.action_view.annotate_rendered_view_with_filenames = true
59 | end
60 |
--------------------------------------------------------------------------------
/app/views/tokite/rules/_form.html.haml:
--------------------------------------------------------------------------------
1 | = form_for [@rule_user, @rule] do |f|
2 | = form_text_field f, :name, size: 40, class: "input"
3 | = form_text_field f, :query, size: 400, class: "input"
4 | = form_text_field f, :channel, size: 40, class: "input"
5 | = form_text_field f, :icon_emoji, size: 40, class: "input"
6 | = form_text_field f, :additional_text, size: 40, class: "input"
7 | = form_text_field f, :display_name, size: 40, class: "input"
8 | .field.columns
9 | .column.is-8= f.submit "Update", class: "button is-primary"
10 | - if @rule.persisted?
11 | .column.is-2= link_to "Transfer", new_rule_transfers_path(@rule), class: "button is-primary"
12 | .column.is-2= link_to "Delete", rule_path(@rule), method: :delete, data: { confirm: %(Delete "#{@rule.name}"?) }, class: "button is-danger"
13 |
14 | .message.my-6
15 | .message-header
16 | Query description
17 | .message-body
18 | %h2.title Supported query type
19 | %table.table.is-bordered
20 | %tr
21 | %th Name
22 | %th Example
23 | %tr
24 | %td Plain word
25 | %td hoge fuga moge
26 | %tr
27 | %td Quoted word
28 | %td "hoge fuga moge"
29 | %tr
30 | %td Regular expression word
31 | %td /(hoge|fuga|moge)/
32 | %tr
33 | %td Exclude word
34 | %td -/(hoge|fuga|moge)/ -user:hogelog
35 |
36 | %h2 Supported query field
37 | %table.table.is-bordered
38 | %tr
39 | %th Field
40 | %th Description
41 | %th Example
42 | %tr
43 | %td repo:
44 | %td Match repository name.
45 | %td repo:cookpad/tokite
46 | %tr
47 | %td title:
48 | %td Match pull_request or issues title.
49 | %td title:Bug
50 | %tr
51 | %td event:
52 | %td Match event type pull_request, issues, issue_comment, pull_request_review, pull_request_review_comment.
53 | %td event:/pull_request|issues|pull_request_review|pull_request_review_comment/
54 | %tr
55 | %td body:
56 | %td Match body text.
57 | %td body:"review please"
58 | %tr
59 | %td user:
60 | %td Match user name.
61 | %td user:hogelog
62 | %tr
63 | %td label:
64 | %td Match pull_request or issue label.
65 | %td label:Feature
66 | %tr
67 | %td review_state:
68 | %td Match pull_request_review state.
69 | %td review_state:/commented|approved|changes_requested/
70 | %tr
71 | %td requested_reviewer:
72 | %td Match user name of review requested reviewer.
73 | %td requested_reviewer:hogelog
74 | %tr
75 | %td requested_team:
76 | %td Match team name of review requested team.
77 | %td requested_team:cookpad/chef
78 | %tr
79 | %td unspecified
80 | %td Match title or body field.
81 | %td review please
82 |
--------------------------------------------------------------------------------
/app/models/tokite/search_query.rb:
--------------------------------------------------------------------------------
1 | require "parslet"
2 |
3 | module Tokite
4 | class SearchQuery
5 | attr_reader :query, :tree
6 |
7 | DEFAULT_FIELDS = %i(title body)
8 |
9 | class QueryError < StandardError
10 | end
11 | class QueryParseError < QueryError
12 | end
13 | class QueryRegexpError < QueryError
14 | end
15 |
16 | class Parser < Parslet::Parser
17 | rule(:space) { match('\s').repeat(1) }
18 | rule(:space?) { space.maybe }
19 |
20 | rule(:quot) { str('"') }
21 | rule(:quoted_char) { (str('\\').ignore >> any) | match('[^"]') }
22 | rule(:char) { match('[^\s]') }
23 | rule(:slash) { str('/') }
24 | rule(:regexp_char) { (str('\\').ignore >> slash) | match('[^/]') }
25 |
26 | rule(:regexp_word) { slash >> regexp_char.repeat(1).as(:regexp_word) >> slash }
27 | rule(:quot_word) { quot >> quoted_char.repeat(1).as(:word) >> quot }
28 | rule(:plain_word) { (match('[^\s/"]') >> match('[^\s]').repeat).as(:word) }
29 | rule(:exclude) { match('-').as(:exclude) }
30 | rule(:field) { match('\w').repeat(1).as(:field) }
31 | rule(:word) { exclude.maybe >> (field >> str(':') >> space?).maybe >> (regexp_word | quot_word | plain_word) }
32 |
33 | rule(:query) { space? >> word >> (space >> word).repeat >> space? }
34 | root :query
35 | end
36 |
37 | def self.parser
38 | @parser ||= Tokite::SearchQuery::Parser.new
39 | end
40 |
41 | def self.parse(query)
42 | Array.wrap(parser.parse(query))
43 | rescue Parslet::ParseFailed => e
44 | raise QueryParseError, e
45 | end
46 |
47 | def self.validate(query)
48 | tree = SearchQuery.parse(query)
49 | tree.each do |word|
50 | Regexp.compile(word[:regexp_word].to_s, Regexp::IGNORECASE) if word[:regexp_word]
51 | end
52 | rescue RegexpError => e
53 | raise QueryRegexpError, e
54 | end
55 |
56 | def initialize(query)
57 | @query = query
58 | @tree = Array.wrap(self.class.parse(query))
59 | end
60 |
61 | def match?(doc)
62 | tree.all? do |word|
63 | field = word[:field]
64 | if field
65 | targets =
66 | case doc[field.to_sym]
67 | when Array
68 | doc[field.to_sym].map(&:downcase)
69 | when nil
70 | []
71 | else
72 | [doc[field.to_sym].downcase]
73 | end
74 | else
75 | targets = DEFAULT_FIELDS.map{|field| doc[field]&.downcase }.compact
76 | end
77 | if word[:regexp_word]
78 | begin
79 | regexp = Regexp.compile(word[:regexp_word].to_s, Regexp::IGNORECASE)
80 | matched = targets.any?{|text| regexp.match?(text) }
81 | rescue RegexpError
82 | matched = false
83 | end
84 | else
85 | value = word[:word].to_s.downcase
86 | matched = targets.any?{|text| text.index(value) }
87 | end
88 | word[:exclude].present? ? !matched : matched
89 | end
90 | end
91 | end
92 | end
93 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # In the development environment your application's code is reloaded any time
7 | # it changes. This slows down response time but is perfect for development
8 | # since you don't have to restart the web server when you make code changes.
9 | config.cache_classes = false
10 |
11 | # Do not eager load code on boot.
12 | config.eager_load = false
13 |
14 | # Show full error reports.
15 | config.consider_all_requests_local = true
16 |
17 | # Enable/disable caching. By default caching is disabled.
18 | # Run rails dev:cache to toggle caching.
19 | if Rails.root.join('tmp', 'caching-dev.txt').exist?
20 | config.action_controller.perform_caching = true
21 | config.action_controller.enable_fragment_cache_logging = true
22 |
23 | config.cache_store = :memory_store
24 | config.public_file_server.headers = {
25 | 'Cache-Control' => "public, max-age=#{2.days.to_i}"
26 | }
27 | else
28 | config.action_controller.perform_caching = false
29 |
30 | config.cache_store = :null_store
31 | end
32 |
33 | # Store uploaded files on the local file system (see config/storage.yml for options).
34 | config.active_storage.service = :local
35 |
36 | # Don't care if the mailer can't send.
37 | config.action_mailer.raise_delivery_errors = false
38 |
39 | config.action_mailer.perform_caching = false
40 |
41 | # Print deprecation notices to the Rails logger.
42 | config.active_support.deprecation = :log
43 |
44 | # Raise exceptions for disallowed deprecations.
45 | config.active_support.disallowed_deprecation = :raise
46 |
47 | # Tell Active Support which deprecation messages to disallow.
48 | config.active_support.disallowed_deprecation_warnings = []
49 |
50 | # Raise an error on page load if there are pending migrations.
51 | config.active_record.migration_error = :page_load
52 |
53 | # Highlight code that triggered database queries in logs.
54 | config.active_record.verbose_query_logs = true
55 |
56 | # Debug mode disables concatenation and preprocessing of assets.
57 | # This option may cause significant delays in view rendering with a large
58 | # number of complex assets.
59 | config.assets.debug = true
60 |
61 | # Suppress logger output for asset requests.
62 | config.assets.quiet = true
63 |
64 | # Raises error for missing translations.
65 | # config.i18n.raise_on_missing_translations = true
66 |
67 | # Annotate rendered view with file names.
68 | # config.action_view.annotate_rendered_view_with_filenames = true
69 |
70 | # Use an evented file watcher to asynchronously detect changes in source code,
71 | # routes, locales, etc. This feature depends on the listen gem.
72 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
73 |
74 | # Uncomment if you wish to allow Action Cable access from any origin.
75 | # config.action_cable.disable_request_forgery_protection = true
76 | end
77 |
--------------------------------------------------------------------------------
/spec/models/tokite/search_query_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Tokite::SearchQuery, type: :model do
4 | describe ".parse" do
5 | subject { Tokite::SearchQuery.parse(query) }
6 |
7 | context "spaced words" do
8 | let(:query) { 'foo bar' }
9 |
10 | it do
11 | expect(subject.size).to eq(2)
12 | expect(subject[0][:word].to_s).to eq('foo')
13 | expect(subject[1][:word].to_s).to eq('bar')
14 | end
15 | end
16 |
17 | context "quoted text" do
18 | let(:query) { '"foo bar"' }
19 |
20 | it do
21 | expect(subject.size).to eq(1)
22 | expect(subject.first[:word].to_s).to eq('foo bar')
23 | end
24 | end
25 |
26 | context "quoted text with escaped quot" do
27 | let(:query) { '"foo\ \"bar\""' }
28 |
29 | it do
30 | expect(subject.size).to eq(1)
31 | expect(subject.first[:word].to_s).to eq('foo "bar"')
32 | end
33 | end
34 |
35 | context "regular expression" do
36 | let(:query) { '/foo bar/' }
37 |
38 | it do
39 | expect(subject.size).to eq(1)
40 | expect(subject.first[:regexp_word].to_s).to eq('foo bar')
41 | end
42 | end
43 |
44 | context "regular expression with escaped character" do
45 | let(:query) { '/\Afoo \/bar\/\z/' }
46 |
47 | it do
48 | expect(subject.size).to eq(1)
49 | expect(subject.first[:regexp_word].to_s).to eq('\Afoo /bar/\z')
50 | end
51 | end
52 |
53 | context "with field" do
54 | let(:query) { 'foo: bar' }
55 |
56 | it do
57 | expect(subject.size).to eq(1)
58 | expect(subject.first[:field].to_s).to eq('foo')
59 | expect(subject.first[:word].to_s).to eq('bar')
60 | end
61 | end
62 |
63 | context "with -word" do
64 | let(:query) { '-foo:bar' }
65 |
66 | it do
67 | expect(subject.size).to eq(1)
68 | expect(subject.first[:field].to_s).to eq('foo')
69 | expect(subject.first[:word].to_s).to eq('bar')
70 | expect(subject.first[:exclude]).to be_present
71 | end
72 | end
73 |
74 | context "with prefix and suffix space" do
75 | let(:query) { ' bar ' }
76 |
77 | it do
78 | expect(subject.size).to eq(1)
79 | expect(subject.first[:word].to_s).to eq('bar')
80 | end
81 | end
82 |
83 | context "with some words" do
84 | let(:query) { ' /./ -foo:"hoge fuga" ' }
85 |
86 | it do
87 | expect(subject.size).to eq(2)
88 | expect(subject[0][:field]).not_to be_present
89 | expect(subject[0][:regexp_word].to_s).to eq('.')
90 | expect(subject[0][:word]).not_to be_present
91 | expect(subject[0][:exclude]).not_to be_present
92 | expect(subject[1][:field].to_s).to eq("foo")
93 | expect(subject[1][:regexp_word]).not_to be_present
94 | expect(subject[1][:word].to_s).to eq("hoge fuga")
95 | expect(subject[1][:exclude]).to be_present
96 | end
97 | end
98 | end
99 | end
100 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 | ## [0.7.1] - 2023-06-15
11 |
12 | ### Changed
13 |
14 | - Upgrade omniauth-github to v2 (https://github.com/cookpad/tokite/pull/66).
15 | - Please add omniauth-rails_csrf_protection gem to your Gemfile.
16 | - Use Ruby 3.1 for testing (https://github.com/cookpad/tokite/pull/67).
17 |
18 | ## [0.7.0] - 2023-06-14
19 |
20 | ### Changed
21 |
22 | - **BREAKING**: Upgrade to Rails 6.1 (https://github.com/cookpad/tokite/pull/64).
23 |
24 | ## [0.6.0] - 2023-06-14
25 |
26 | ### Changed
27 |
28 | - **BREAKING**: Upgrade to Rails 6.0 (https://github.com/cookpad/tokite/pull/61).
29 |
30 | ## [0.5.1] - 2023-06-14
31 |
32 | ### Changed
33 |
34 | - Upgrade bulma to 0.9.4 (https://github.com/cookpad/tokite/pull/59).
35 |
36 | ## [0.5.0] - 2023-06-14
37 |
38 | We have not released a new version of tokite gem for a long time. Be careful when upgrading to this version from previous versions.
39 |
40 | ### Added
41 |
42 | - **BREAKING**: Add requested_reviewer and requested_team to query field names (https://github.com/cookpad/tokite/pull/53).
43 | - If you feel these notification are too noisy, please report an issue.
44 | - Add badges for private repositories (https://github.com/cookpad/tokite/pull/41).
45 | - Add input form for editting display name in Slack channel (https://github.com/cookpad/tokite/pull/40).
46 | - Support label query (https://github.com/cookpad/tokite/pull/51).
47 | - Use issue labels info also for issue_comment event (https://github.com/cookpad/tokite/pull/56).
48 |
49 | ### Changed
50 |
51 | - **BREAKING**: Upgrade to Rails 5.2 (https://github.com/cookpad/tokite/pull/57).
52 | - Loose length limit of icon_emoji (https://github.com/cookpad/tokite/pull/46).
53 | - Upgrade rspec version for development (https://github.com/cookpad/tokite/pull/47).
54 | - Use Ruby 2.7 (https://github.com/cookpad/tokite/pull/52).
55 | - Use GitHub Actions for CI (https://github.com/cookpad/tokite/pull/54).
56 |
57 | ### Fixed
58 |
59 | - Fix to treat nil text body in pull_request_review hook (https://github.com/cookpad/tokite/pull/38).
60 | - Fix to check if a repository is archived or not (https://github.com/cookpad/tokite/pull/39).
61 | - Fix to show error message when no repository is selected in `/repositories/new` page (https://github.com/cookpad/tokite/pull/42).
62 | - Fix an error occurred when a query contains wrong regular expressions (https://github.com/cookpad/tokite/pull/43).
63 |
64 |
65 | [Unreleased]: https://github.com/cookpad/tokite/compare/v0.7.1...HEAD
66 | [0.7.1]: https://github.com/cookpad/tokite/compare/v0.7.0...v0.7.1
67 | [0.7.0]: https://github.com/cookpad/tokite/compare/v0.6.0...v0.7.0
68 | [0.6.0]: https://github.com/cookpad/tokite/compare/v0.5.1...v0.6.0
69 | [0.5.1]: https://github.com/cookpad/tokite/compare/v0.5.0...v0.5.1
70 | [0.5.0]: https://github.com/cookpad/tokite/compare/v0.4.1...v0.5.0
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tokite [](https://badge.fury.io/rb/tokite)
2 |
3 | Tokite send GitHub event (pull-request, issue and comment) to Slack.
4 |
5 | Notification setting are personalized and customizable by query.
6 |
7 | ## Installation
8 | Tokite works as rails mountable engine.
9 |
10 | Add this line to your rails application's Gemfile:
11 | ```ruby
12 | gem "tokite"
13 | ```
14 |
15 | And mount engine.
16 |
17 | ```ruby
18 | Rails.application.routes.draw do
19 | mount Tokite::Engine => "/"
20 | end
21 | ```
22 |
23 | ### Setup database
24 | ```console
25 | $ ./bin/rails db:create
26 | $ ./bin/rails app:tokite:ridgepole:install
27 | $ ./bin/rails app:tokite:ridgepole:apply
28 | $ RAILS_ENV=test ./bin/rails app:tokite:ridgepole:apply
29 | ```
30 |
31 | ### Setup yarn pkg
32 | ```console
33 | $ ./bin/rails tokite:yarn:install
34 | ```
35 |
36 | ## Configuration
37 |
38 | | GITHUB_CLIENT_ID | GitHub OAuth2 client ID |
39 | | GITHUB_CLIENT_SECRET | GitHub OAuth2 client secret |
40 | | GITHUB_HOST (optional) | GitHub Enterprise host |
41 | | SECRET_KEY_BASE | rails secret key |
42 | | SLACK_WEBHOOK_URL | Slack incoming webhook url |
43 | | SLACK_NAME (optional) | Slack notification user name |
44 | | SLACK_ICON_EMOJI (optional) | Slack notification icon |
45 | | APP_HOST (optional) | Application host url |
46 |
47 |
48 | ## Usage
49 | ### Supported Event
50 |
51 | Tokite support only below events now.
52 |
53 | - pull_request
54 | - issues
55 | - issue_comment
56 |
57 | ### Supported query type
58 |
59 |
60 | | Name | Example |
61 | | Plain word | hoge fuga moge |
62 | | Quoted word | "hoge fuga moge" |
63 | | Regular expression word | /hoge|fuga|moge/ |
64 | | Exclude word | -/(hoge|fuga|moge)/ -user:hogelog |
65 |
66 |
67 | ### Supported query field
68 |
69 |
70 | | Name | Description | Example |
71 | | repo: | Match repository name. | repo:cookpad/tokite |
72 | | title: | Match pull_request or issues title. | title:Bug |
73 | | event: | Match event type pull_request, issues, issue_comment, pull_request_review, pull_request_review_comment. | event:/pull_request|issues|pull_request_review|pull_request_review_comment/ |
74 | | body: | Match body text. | body:"review please" |
75 | | user: | Match user name. | user:hogelog |
76 | | label: | Match pull_request or issue label. | label:Feature |
77 | | review_state: | Match pull_request_review state. | review_state:/commented|approved|changes_requested/ |
78 | | requested_reviewer: | Match user name of review requested reviewer | requested_reviewer:hogelog |
79 | | requested_team: | Match team name of review requested team | requested_team:cookpad/chef |
80 | | unspecified | Match title or body field. | review please |
81 |
82 |
--------------------------------------------------------------------------------
/spec/models/tokite/rule_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Tokite::Rule, type: :model do
4 | let(:hook_payload) { JSON.parse(payload_json("pull_request.json")).with_indifferent_access }
5 | let(:hook_event) { Tokite::HookEvent::PullRequest.new(hook_payload) }
6 |
7 | describe ".matched_rules" do
8 | before do
9 | FactoryBot.create(:rule, query: "foo")
10 | FactoryBot.create(:rule, query: "(?:foo|bar)")
11 | FactoryBot.create(:rule, query: "/(?:foo|bar)/")
12 | FactoryBot.create(:rule, query: "bar")
13 | hook_event.hook_params[:pull_request][:title] = "This is foo."
14 | end
15 |
16 | it "returns only matched rules" do
17 | rules = Tokite::Rule.matched_rules(hook_event)
18 | expect(rules.size).to eq(2)
19 | end
20 | end
21 |
22 | describe "#match?" do
23 | let(:title) { "title" }
24 | let(:body) { "body" }
25 | let(:user_login) { "hogelog" }
26 | let(:rule) { FactoryBot.create(:rule, query: query) }
27 | subject { rule.match?(hook_event) }
28 | before do
29 | hook_event.hook_params[:pull_request][:title] = title
30 | hook_event.hook_params[:pull_request][:body] = body
31 | hook_event.hook_params[:pull_request][:user][:login] = user_login
32 | end
33 |
34 | context "with matched single word" do
35 | let(:query) { "title" }
36 | it { is_expected.to eq(true) }
37 | end
38 |
39 | context "with unmatched single word" do
40 | let(:query) { "foobar" }
41 | it { is_expected.to eq(false) }
42 | end
43 |
44 | context "with matched multiple words" do
45 | let(:query) { "title body" }
46 | it { is_expected.to eq(true) }
47 | end
48 |
49 | context "with unmatched multiple words" do
50 | let(:query) { "title body foobar" }
51 | it { is_expected.to eq(false) }
52 | end
53 |
54 | context "with unlabeled word" do
55 | let(:query) { "hogelog" }
56 | it { is_expected.not_to eq(true) }
57 | end
58 |
59 | context "with matched labeled word" do
60 | let(:query) { "title:title body:body user:hogelog" }
61 | it { is_expected.to eq(true) }
62 | end
63 |
64 | context "with unmatched labeled word" do
65 | let(:query) { "title:body" }
66 | it { is_expected.to eq(false) }
67 | end
68 |
69 | context "with matched quoted word" do
70 | let(:query) { %("title title") }
71 | before do
72 | hook_event.hook_params[:pull_request][:title] = %(title title)
73 | end
74 | it { is_expected.to eq(true) }
75 | end
76 |
77 | context "with unmatched quoted word" do
78 | let(:query) { %("title title") }
79 | it { is_expected.to eq(false) }
80 | end
81 |
82 | context "with case unmatched word" do
83 | let(:query) { "TITLE" }
84 | it { is_expected.to eq(true) }
85 | end
86 |
87 | context "with case unmatched regular expression" do
88 | let(:query) { '/\ATITLE\z/' }
89 | it { is_expected.to eq(true) }
90 | end
91 |
92 | context "with backslash regular expression" do
93 | let(:query) { 'body:/\AThis is \w+.\z/' }
94 | let(:body) { "This is body." }
95 | it { is_expected.to eq(true) }
96 | end
97 |
98 | context "with matched exclude word" do
99 | let(:query) { 'body:/\AThis is \w+.\z/ -user:/hogelog/' }
100 | let(:body) { "This is body." }
101 | it { is_expected.to eq(false) }
102 | end
103 |
104 | context "with unmatched exclude word" do
105 | let(:query) { 'body:/\AThis is \w+.\z/ -user:fugalog' }
106 | let(:body) { "This is body." }
107 | it { is_expected.to eq(true) }
108 | end
109 | end
110 |
111 | describe "validates :query" do
112 | let(:query) { "foo bar" }
113 | let(:rule) { FactoryBot.build(:rule, query: query) }
114 |
115 | it { expect(rule).to be_valid }
116 |
117 | context "with unclosed quoted text" do
118 | let(:query) { 'foobar "foo bar' }
119 |
120 | it { expect(rule).not_to be_valid }
121 | end
122 |
123 | context "with unclosed regular expression" do
124 | let(:query) { 'foobar /foo bar' }
125 |
126 | it { expect(rule).not_to be_valid }
127 | end
128 | end
129 | end
130 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # Code is not reloaded between requests.
7 | config.cache_classes = true
8 |
9 | # Eager load code on boot. This eager loads most of Rails and
10 | # your application in memory, allowing both threaded web servers
11 | # and those relying on copy on write to perform better.
12 | # Rake tasks automatically ignore this option for performance.
13 | config.eager_load = true
14 |
15 | # Full error reports are disabled and caching is turned on.
16 | config.consider_all_requests_local = false
17 | config.action_controller.perform_caching = true
18 |
19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
21 | # config.require_master_key = true
22 |
23 | # Disable serving static files from the `/public` folder by default since
24 | # Apache or NGINX already handles this.
25 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
26 |
27 | # Compress CSS using a preprocessor.
28 | # config.assets.css_compressor = :sass
29 |
30 | # Do not fallback to assets pipeline if a precompiled asset is missed.
31 | config.assets.compile = false
32 |
33 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
34 | # config.asset_host = 'http://assets.example.com'
35 |
36 | # Specifies the header that your server uses for sending files.
37 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
38 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
39 |
40 | # Store uploaded files on the local file system (see config/storage.yml for options).
41 | config.active_storage.service = :local
42 |
43 | # Mount Action Cable outside main process or domain.
44 | # config.action_cable.mount_path = nil
45 | # config.action_cable.url = 'wss://example.com/cable'
46 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
47 |
48 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
49 | # config.force_ssl = true
50 |
51 | # Include generic and useful information about system operation, but avoid logging too much
52 | # information to avoid inadvertent exposure of personally identifiable information (PII).
53 | config.log_level = :info
54 |
55 | # Prepend all log lines with the following tags.
56 | config.log_tags = [ :request_id ]
57 |
58 | # Use a different cache store in production.
59 | # config.cache_store = :mem_cache_store
60 |
61 | # Use a real queuing backend for Active Job (and separate queues per environment).
62 | # config.active_job.queue_adapter = :resque
63 | # config.active_job.queue_name_prefix = "dummy_production"
64 |
65 | config.action_mailer.perform_caching = false
66 |
67 | # Ignore bad email addresses and do not raise email delivery errors.
68 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
69 | # config.action_mailer.raise_delivery_errors = false
70 |
71 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
72 | # the I18n.default_locale when a translation cannot be found).
73 | config.i18n.fallbacks = true
74 |
75 | # Send deprecation notices to registered listeners.
76 | config.active_support.deprecation = :notify
77 |
78 | # Log disallowed deprecations.
79 | config.active_support.disallowed_deprecation = :log
80 |
81 | # Tell Active Support which deprecation messages to disallow.
82 | config.active_support.disallowed_deprecation_warnings = []
83 |
84 | # Use default logging formatter so that PID and timestamp are not suppressed.
85 | config.log_formatter = ::Logger::Formatter.new
86 |
87 | # Use a different logger for distributed setups.
88 | # require "syslog/logger"
89 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
90 |
91 | if ENV["RAILS_LOG_TO_STDOUT"].present?
92 | logger = ActiveSupport::Logger.new(STDOUT)
93 | logger.formatter = config.log_formatter
94 | config.logger = ActiveSupport::TaggedLogging.new(logger)
95 | end
96 |
97 | # Do not dump schema after migrations.
98 | config.active_record.dump_schema_after_migration = false
99 |
100 | # Inserts middleware to perform automatic connection switching.
101 | # The `database_selector` hash is used to pass options to the DatabaseSelector
102 | # middleware. The `delay` is used to determine how long to wait after a write
103 | # to send a subsequent read to the primary.
104 | #
105 | # The `database_resolver` class is used by the middleware to determine which
106 | # database is appropriate to use based on the time delay.
107 | #
108 | # The `database_resolver_context` class is used by the middleware to set
109 | # timestamps for the last write to the primary. The resolver uses the context
110 | # class timestamps to determine how long to wait before reading from the
111 | # replica.
112 | #
113 | # By default Rails will store a last write timestamp in the session. The
114 | # DatabaseSelector middleware is designed as such you can define your own
115 | # strategy for connection switching and pass that into the middleware through
116 | # these configuration options.
117 | # config.active_record.database_selector = { delay: 2.seconds }
118 | # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
119 | # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
120 | end
121 |
--------------------------------------------------------------------------------
/spec/requests/hooks_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 | require 'json'
3 |
4 | RSpec.describe "Hook", type: :request do
5 | describe "#create" do
6 | let(:headers) {
7 | { Tokite::HooksController::GITHUB_EVENT_HEADER => event }
8 | }
9 | let(:params) {
10 | JSON.parse(payload_json("#{event}.json"))
11 | }
12 | let!(:rule) { FactoryBot.create(:rule, query: query, icon_emoji: ":snail:") }
13 |
14 | context "with pull_request" do
15 | let(:event) { "pull_request" }
16 | let(:query) { %(event:pull_request repo:hogelog/test-repo user:hogelog title:/./ body:/./ /./ -"unmatched word") }
17 |
18 | it "fires a hook" do
19 | expect_any_instance_of(Tokite::Hook).to receive(:fire!).and_call_original
20 | expect_any_instance_of(Tokite::NotifyGithubHookEventJob).to receive(:perform)
21 | post hooks_path, params: params, headers: headers, as: :json
22 | end
23 |
24 | context "without body comment" do
25 | let(:query) { %(event:pull_request user:hogelog) }
26 | before do
27 | params["pull_request"].delete("body")
28 | end
29 |
30 | it "fires a hook" do
31 | expect_any_instance_of(Tokite::Hook).to receive(:fire!).and_call_original
32 | expect_any_instance_of(Tokite::NotifyGithubHookEventJob).to receive(:perform)
33 | post hooks_path, params: params, headers: headers, as: :json
34 | end
35 | end
36 | end
37 |
38 | context "with issues" do
39 | let(:event) { "issues" }
40 | let(:query) { %(event:issues repo:hogelog/test-repo user:hogelog title:/./ body:/./ /./ -"unmatched word") }
41 |
42 | it "fires a hook" do
43 | expect_any_instance_of(Tokite::Hook).to receive(:fire!).and_call_original
44 | expect_any_instance_of(Tokite::NotifyGithubHookEventJob).to receive(:perform)
45 | post hooks_path, params: params, headers: headers, as: :json
46 | end
47 |
48 | context "without body comment" do
49 | let(:query) { %(event:issues user:hogelog) }
50 | before do
51 | params["issue"].delete("body")
52 | end
53 |
54 | it "fires a hook" do
55 | expect_any_instance_of(Tokite::Hook).to receive(:fire!).and_call_original
56 | expect_any_instance_of(Tokite::NotifyGithubHookEventJob).to receive(:perform)
57 | post hooks_path, params: params, headers: headers, as: :json
58 | end
59 | end
60 | end
61 |
62 | context "with issue_comment" do
63 | let(:event) { "issue_comment" }
64 | let(:query) { %(event:issue_comment repo:hogelog/test-repo user:hogelog body:/./ /./ -"unmatched word") }
65 |
66 | it "fires a hook" do
67 | expect_any_instance_of(Tokite::Hook).to receive(:fire!).and_call_original
68 | expect_any_instance_of(Tokite::NotifyGithubHookEventJob).to receive(:perform)
69 | post hooks_path, params: params, headers: headers, as: :json
70 | end
71 | end
72 |
73 | context "with pull_request_review" do
74 | let(:event) { "pull_request_review" }
75 | let(:query) { %(event:pull_request_review repo:hogelog/test-repo user:hogelog review_state:commented body:/./ /./ -"unmatched word") }
76 |
77 | it "fires a hook" do
78 | expect_any_instance_of(Tokite::Hook).to receive(:fire!).and_call_original
79 | expect_any_instance_of(Tokite::NotifyGithubHookEventJob).to receive(:perform)
80 | post hooks_path, params: params, headers: headers, as: :json
81 | end
82 |
83 | context "without body comment" do
84 | let(:query) { %(event:pull_request_review user:hogelog) }
85 | before do
86 | params["review"].delete("body")
87 | end
88 |
89 | it "fires a hook" do
90 | expect_any_instance_of(Tokite::Hook).to receive(:fire!).and_call_original
91 | expect_any_instance_of(Tokite::NotifyGithubHookEventJob).not_to receive(:perform)
92 | post hooks_path, params: params, headers: headers, as: :json
93 | end
94 | end
95 | end
96 |
97 | context "with pull_request_review_comment" do
98 | let(:event) { "pull_request_review_comment" }
99 | let(:query) { %(event:pull_request_review_comment repo:hogelog/test-repo user:hogelog body:/./ /./ -"unmatched word") }
100 |
101 | it "fires a hook" do
102 | expect_any_instance_of(Tokite::Hook).to receive(:fire!).and_call_original
103 | expect_any_instance_of(Tokite::NotifyGithubHookEventJob).to receive(:perform)
104 | post hooks_path, params: params, headers: headers, as: :json
105 | end
106 | end
107 |
108 | context "with duplicated rules" do
109 | let(:event) { "issue_comment" }
110 | let(:query) { %(event:issue_comment repo:hogelog/test-repo user:hogelog body:/./ /./ -"unmatched word") }
111 | let!(:duplicated_rule) { FactoryBot.create(:rule, query: query, icon_emoji: ":snail:") }
112 |
113 | it "preserves duplicated notification" do
114 | expect(Tokite::NotifyGithubHookEventJob).to receive(:perform_now).once
115 | post hooks_path, params: params, headers: headers, as: :json
116 | end
117 | end
118 |
119 | context "when slack returns error" do
120 | let(:event) { "pull_request" }
121 | let(:query) { %(event:pull_request repo:hogelog/test-repo user:hogelog title:/./ body:/./ /./ -"unmatched word") }
122 |
123 | it "captures exception" do
124 | expect_any_instance_of(Tokite::Hook).to receive(:fire!).and_call_original
125 | expect_any_instance_of(Slack::Notifier).to receive(:ping).and_raise(Slack::Notifier::APIError)
126 | expect(Tokite::ExceptionLogger).to receive(:log).once
127 | post hooks_path, params: params, headers: headers, as: :json
128 | end
129 | end
130 |
131 | context "with label rule" do
132 | let(:event) { "issues" }
133 | let(:query) { %(label:/bug|foobar/) }
134 |
135 | it "fires a hook" do
136 | expect_any_instance_of(Tokite::Hook).to receive(:fire!).and_call_original
137 | expect_any_instance_of(Tokite::NotifyGithubHookEventJob).to receive(:perform)
138 | post hooks_path, params: params, headers: headers, as: :json
139 | end
140 | end
141 |
142 | context "with unmatched label rule" do
143 | let(:event) { "issues" }
144 | let(:query) { %(label:/foo|bar/) }
145 |
146 | it "doesn't notify to slack" do
147 | expect_any_instance_of(Tokite::Hook).to receive(:fire!).and_call_original
148 | expect_any_instance_of(Tokite::NotifyGithubHookEventJob).not_to receive(:perform)
149 | post hooks_path, params: params, headers: headers, as: :json
150 | end
151 | end
152 |
153 | context "with requested_reviewer rule" do
154 | let(:event) { "pull_request" }
155 | let(:query) { %(requested_reviewer:other_user) }
156 |
157 | it "fires a hook" do
158 | expect_any_instance_of(Tokite::Hook).to receive(:fire!).and_call_original
159 | expect_any_instance_of(Tokite::NotifyGithubHookEventJob).to receive(:perform)
160 | post hooks_path, params: params, headers: headers, as: :json
161 | end
162 |
163 | context 'review_requested action' do
164 | let(:params) {
165 | JSON.parse(payload_json("#{event}.review_requested.json"))
166 | }
167 |
168 | it "fires a hook" do
169 | expect_any_instance_of(Tokite::Hook).to receive(:fire!).and_call_original
170 | expect_any_instance_of(Tokite::NotifyGithubHookEventJob).to receive(:perform)
171 | post hooks_path, params: params, headers: headers, as: :json
172 | end
173 | end
174 | end
175 |
176 | context "with requested_team rule" do
177 | let(:event) { "pull_request" }
178 | let(:query) { %(requested_team:github/justice-league) }
179 |
180 | it "fires a hook" do
181 | expect_any_instance_of(Tokite::Hook).to receive(:fire!).and_call_original
182 | expect_any_instance_of(Tokite::NotifyGithubHookEventJob).to receive(:perform)
183 | post hooks_path, params: params, headers: headers, as: :json
184 | end
185 | end
186 | end
187 | end
188 |
--------------------------------------------------------------------------------
/spec/payloads/issues.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "opened",
3 | "issue": {
4 | "url": "https://api.github.com/repos/hogelog/test-repo/issues/3",
5 | "repository_url": "https://api.github.com/repos/hogelog/test-repo",
6 | "labels_url": "https://api.github.com/repos/hogelog/test-repo/issues/3/labels{/name}",
7 | "comments_url": "https://api.github.com/repos/hogelog/test-repo/issues/3/comments",
8 | "events_url": "https://api.github.com/repos/hogelog/test-repo/issues/3/events",
9 | "html_url": "https://github.com/hogelog/test-repo/issues/3",
10 | "id": 233433516,
11 | "number": 3,
12 | "title": "Test",
13 | "user": {
14 | "login": "hogelog",
15 | "id": 50920,
16 | "avatar_url": "https://avatars2.githubusercontent.com/u/50920?v=3",
17 | "gravatar_id": "",
18 | "url": "https://api.github.com/users/hogelog",
19 | "html_url": "https://github.com/hogelog",
20 | "followers_url": "https://api.github.com/users/hogelog/followers",
21 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
22 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
23 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
24 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
25 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
26 | "repos_url": "https://api.github.com/users/hogelog/repos",
27 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
28 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
29 | "type": "User",
30 | "site_admin": false
31 | },
32 | "labels": [
33 | {
34 | "id": 1362934389,
35 | "node_id": "MDU6TGFiZWwxMzYyOTM0Mzg5",
36 | "url": "https://api.github.com/repos/Codertocat/Hello-World/labels/bug",
37 | "name": "bug",
38 | "color": "d73a4a",
39 | "default": true
40 | },
41 | {
42 | "id": 1362934390,
43 | "node_id": "MDU6TGFiZWwxMzYyOTM0Mzg6",
44 | "url": "https://api.github.com/repos/Codertocat/Hello-World/labels/feature",
45 | "name": "feature",
46 | "color": "d73a4b",
47 | "default": true
48 | }
49 | ],
50 | "state": "open",
51 | "locked": false,
52 | "assignee": null,
53 | "assignees": [
54 |
55 | ],
56 | "milestone": null,
57 | "comments": 0,
58 | "created_at": "2017-06-04T13:26:46Z",
59 | "updated_at": "2017-06-04T13:26:46Z",
60 | "closed_at": null,
61 | "body": "aaaaaaa\r\nbbbbbb\r\n\r\n\r\nccccccc"
62 | },
63 | "repository": {
64 | "id": 93059937,
65 | "name": "test-repo",
66 | "full_name": "hogelog/test-repo",
67 | "owner": {
68 | "login": "hogelog",
69 | "id": 50920,
70 | "avatar_url": "https://avatars2.githubusercontent.com/u/50920?v=3",
71 | "gravatar_id": "",
72 | "url": "https://api.github.com/users/hogelog",
73 | "html_url": "https://github.com/hogelog",
74 | "followers_url": "https://api.github.com/users/hogelog/followers",
75 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
76 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
77 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
78 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
79 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
80 | "repos_url": "https://api.github.com/users/hogelog/repos",
81 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
82 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
83 | "type": "User",
84 | "site_admin": false
85 | },
86 | "private": false,
87 | "html_url": "https://github.com/hogelog/test-repo",
88 | "description": null,
89 | "fork": false,
90 | "url": "https://api.github.com/repos/hogelog/test-repo",
91 | "forks_url": "https://api.github.com/repos/hogelog/test-repo/forks",
92 | "keys_url": "https://api.github.com/repos/hogelog/test-repo/keys{/key_id}",
93 | "collaborators_url": "https://api.github.com/repos/hogelog/test-repo/collaborators{/collaborator}",
94 | "teams_url": "https://api.github.com/repos/hogelog/test-repo/teams",
95 | "hooks_url": "https://api.github.com/repos/hogelog/test-repo/hooks",
96 | "issue_events_url": "https://api.github.com/repos/hogelog/test-repo/issues/events{/number}",
97 | "events_url": "https://api.github.com/repos/hogelog/test-repo/events",
98 | "assignees_url": "https://api.github.com/repos/hogelog/test-repo/assignees{/user}",
99 | "branches_url": "https://api.github.com/repos/hogelog/test-repo/branches{/branch}",
100 | "tags_url": "https://api.github.com/repos/hogelog/test-repo/tags",
101 | "blobs_url": "https://api.github.com/repos/hogelog/test-repo/git/blobs{/sha}",
102 | "git_tags_url": "https://api.github.com/repos/hogelog/test-repo/git/tags{/sha}",
103 | "git_refs_url": "https://api.github.com/repos/hogelog/test-repo/git/refs{/sha}",
104 | "trees_url": "https://api.github.com/repos/hogelog/test-repo/git/trees{/sha}",
105 | "statuses_url": "https://api.github.com/repos/hogelog/test-repo/statuses/{sha}",
106 | "languages_url": "https://api.github.com/repos/hogelog/test-repo/languages",
107 | "stargazers_url": "https://api.github.com/repos/hogelog/test-repo/stargazers",
108 | "contributors_url": "https://api.github.com/repos/hogelog/test-repo/contributors",
109 | "subscribers_url": "https://api.github.com/repos/hogelog/test-repo/subscribers",
110 | "subscription_url": "https://api.github.com/repos/hogelog/test-repo/subscription",
111 | "commits_url": "https://api.github.com/repos/hogelog/test-repo/commits{/sha}",
112 | "git_commits_url": "https://api.github.com/repos/hogelog/test-repo/git/commits{/sha}",
113 | "comments_url": "https://api.github.com/repos/hogelog/test-repo/comments{/number}",
114 | "issue_comment_url": "https://api.github.com/repos/hogelog/test-repo/issues/comments{/number}",
115 | "contents_url": "https://api.github.com/repos/hogelog/test-repo/contents/{+path}",
116 | "compare_url": "https://api.github.com/repos/hogelog/test-repo/compare/{base}...{head}",
117 | "merges_url": "https://api.github.com/repos/hogelog/test-repo/merges",
118 | "archive_url": "https://api.github.com/repos/hogelog/test-repo/{archive_format}{/ref}",
119 | "downloads_url": "https://api.github.com/repos/hogelog/test-repo/downloads",
120 | "issues_url": "https://api.github.com/repos/hogelog/test-repo/issues{/number}",
121 | "pulls_url": "https://api.github.com/repos/hogelog/test-repo/pulls{/number}",
122 | "milestones_url": "https://api.github.com/repos/hogelog/test-repo/milestones{/number}",
123 | "notifications_url": "https://api.github.com/repos/hogelog/test-repo/notifications{?since,all,participating}",
124 | "labels_url": "https://api.github.com/repos/hogelog/test-repo/labels{/name}",
125 | "releases_url": "https://api.github.com/repos/hogelog/test-repo/releases{/id}",
126 | "deployments_url": "https://api.github.com/repos/hogelog/test-repo/deployments",
127 | "created_at": "2017-06-01T13:26:00Z",
128 | "updated_at": "2017-06-01T14:03:40Z",
129 | "pushed_at": "2017-06-01T15:26:59Z",
130 | "git_url": "git://github.com/hogelog/test-repo.git",
131 | "ssh_url": "git@github.com:hogelog/test-repo.git",
132 | "clone_url": "https://github.com/hogelog/test-repo.git",
133 | "svn_url": "https://github.com/hogelog/test-repo",
134 | "homepage": null,
135 | "size": 1,
136 | "stargazers_count": 0,
137 | "watchers_count": 0,
138 | "language": "Ruby",
139 | "has_issues": true,
140 | "has_projects": true,
141 | "has_downloads": true,
142 | "has_wiki": true,
143 | "has_pages": false,
144 | "forks_count": 0,
145 | "mirror_url": null,
146 | "open_issues_count": 2,
147 | "forks": 0,
148 | "open_issues": 2,
149 | "watchers": 0,
150 | "default_branch": "master"
151 | },
152 | "sender": {
153 | "login": "hogelog",
154 | "id": 50920,
155 | "avatar_url": "https://avatars2.githubusercontent.com/u/50920?v=3",
156 | "gravatar_id": "",
157 | "url": "https://api.github.com/users/hogelog",
158 | "html_url": "https://github.com/hogelog",
159 | "followers_url": "https://api.github.com/users/hogelog/followers",
160 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
161 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
162 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
163 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
164 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
165 | "repos_url": "https://api.github.com/users/hogelog/repos",
166 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
167 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
168 | "type": "User",
169 | "site_admin": false
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/spec/payloads/issue_comment.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "action": "created",
4 | "issue": {
5 | "url": "https://api.github.com/repos/hogelog/test-repo/issues/2",
6 | "repository_url": "https://api.github.com/repos/hogelog/test-repo",
7 | "labels_url": "https://api.github.com/repos/hogelog/test-repo/issues/2/labels{/name}",
8 | "comments_url": "https://api.github.com/repos/hogelog/test-repo/issues/2/comments",
9 | "events_url": "https://api.github.com/repos/hogelog/test-repo/issues/2/events",
10 | "html_url": "https://github.com/hogelog/test-repo/pull/2",
11 | "id": 232918062,
12 | "number": 2,
13 | "title": "fuga",
14 | "user": {
15 | "login": "hogelog",
16 | "id": 50920,
17 | "avatar_url": "https://avatars2.githubusercontent.com/u/50920?v=3",
18 | "gravatar_id": "",
19 | "url": "https://api.github.com/users/hogelog",
20 | "html_url": "https://github.com/hogelog",
21 | "followers_url": "https://api.github.com/users/hogelog/followers",
22 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
23 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
24 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
25 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
26 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
27 | "repos_url": "https://api.github.com/users/hogelog/repos",
28 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
29 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
30 | "type": "User",
31 | "site_admin": false
32 | },
33 | "labels": [
34 |
35 | ],
36 | "state": "open",
37 | "locked": false,
38 | "assignee": null,
39 | "assignees": [
40 |
41 | ],
42 | "milestone": null,
43 | "comments": 0,
44 | "created_at": "2017-06-01T15:26:58Z",
45 | "updated_at": "2017-06-02T14:15:12Z",
46 | "closed_at": null,
47 | "pull_request": {
48 | "url": "https://api.github.com/repos/hogelog/test-repo/pulls/2",
49 | "html_url": "https://github.com/hogelog/test-repo/pull/2",
50 | "diff_url": "https://github.com/hogelog/test-repo/pull/2.diff",
51 | "patch_url": "https://github.com/hogelog/test-repo/pull/2.patch"
52 | },
53 | "body": "abjiefaaaaaa"
54 | },
55 | "comment": {
56 | "url": "https://api.github.com/repos/hogelog/test-repo/issues/comments/305800928",
57 | "html_url": "https://github.com/hogelog/test-repo/pull/2#issuecomment-305800928",
58 | "issue_url": "https://api.github.com/repos/hogelog/test-repo/issues/2",
59 | "id": 305800928,
60 | "user": {
61 | "login": "hogelog",
62 | "id": 50920,
63 | "avatar_url": "https://avatars2.githubusercontent.com/u/50920?v=3",
64 | "gravatar_id": "",
65 | "url": "https://api.github.com/users/hogelog",
66 | "html_url": "https://github.com/hogelog",
67 | "followers_url": "https://api.github.com/users/hogelog/followers",
68 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
69 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
70 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
71 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
72 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
73 | "repos_url": "https://api.github.com/users/hogelog/repos",
74 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
75 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
76 | "type": "User",
77 | "site_admin": false
78 | },
79 | "created_at": "2017-06-02T14:15:12Z",
80 | "updated_at": "2017-06-02T14:15:12Z",
81 | "body": "こんにちはこんにちは\r\n\r\n## タイトル\r\n\r\naaaaaa\r\nbbbbbb\r\nccccc\r\nddddd"
82 | },
83 | "repository": {
84 | "id": 93059937,
85 | "name": "test-repo",
86 | "full_name": "hogelog/test-repo",
87 | "owner": {
88 | "login": "hogelog",
89 | "id": 50920,
90 | "avatar_url": "https://avatars2.githubusercontent.com/u/50920?v=3",
91 | "gravatar_id": "",
92 | "url": "https://api.github.com/users/hogelog",
93 | "html_url": "https://github.com/hogelog",
94 | "followers_url": "https://api.github.com/users/hogelog/followers",
95 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
96 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
97 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
98 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
99 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
100 | "repos_url": "https://api.github.com/users/hogelog/repos",
101 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
102 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
103 | "type": "User",
104 | "site_admin": false
105 | },
106 | "private": false,
107 | "html_url": "https://github.com/hogelog/test-repo",
108 | "description": null,
109 | "fork": false,
110 | "url": "https://api.github.com/repos/hogelog/test-repo",
111 | "forks_url": "https://api.github.com/repos/hogelog/test-repo/forks",
112 | "keys_url": "https://api.github.com/repos/hogelog/test-repo/keys{/key_id}",
113 | "collaborators_url": "https://api.github.com/repos/hogelog/test-repo/collaborators{/collaborator}",
114 | "teams_url": "https://api.github.com/repos/hogelog/test-repo/teams",
115 | "hooks_url": "https://api.github.com/repos/hogelog/test-repo/hooks",
116 | "issue_events_url": "https://api.github.com/repos/hogelog/test-repo/issues/events{/number}",
117 | "events_url": "https://api.github.com/repos/hogelog/test-repo/events",
118 | "assignees_url": "https://api.github.com/repos/hogelog/test-repo/assignees{/user}",
119 | "branches_url": "https://api.github.com/repos/hogelog/test-repo/branches{/branch}",
120 | "tags_url": "https://api.github.com/repos/hogelog/test-repo/tags",
121 | "blobs_url": "https://api.github.com/repos/hogelog/test-repo/git/blobs{/sha}",
122 | "git_tags_url": "https://api.github.com/repos/hogelog/test-repo/git/tags{/sha}",
123 | "git_refs_url": "https://api.github.com/repos/hogelog/test-repo/git/refs{/sha}",
124 | "trees_url": "https://api.github.com/repos/hogelog/test-repo/git/trees{/sha}",
125 | "statuses_url": "https://api.github.com/repos/hogelog/test-repo/statuses/{sha}",
126 | "languages_url": "https://api.github.com/repos/hogelog/test-repo/languages",
127 | "stargazers_url": "https://api.github.com/repos/hogelog/test-repo/stargazers",
128 | "contributors_url": "https://api.github.com/repos/hogelog/test-repo/contributors",
129 | "subscribers_url": "https://api.github.com/repos/hogelog/test-repo/subscribers",
130 | "subscription_url": "https://api.github.com/repos/hogelog/test-repo/subscription",
131 | "commits_url": "https://api.github.com/repos/hogelog/test-repo/commits{/sha}",
132 | "git_commits_url": "https://api.github.com/repos/hogelog/test-repo/git/commits{/sha}",
133 | "comments_url": "https://api.github.com/repos/hogelog/test-repo/comments{/number}",
134 | "issue_comment_url": "https://api.github.com/repos/hogelog/test-repo/issues/comments{/number}",
135 | "contents_url": "https://api.github.com/repos/hogelog/test-repo/contents/{+path}",
136 | "compare_url": "https://api.github.com/repos/hogelog/test-repo/compare/{base}...{head}",
137 | "merges_url": "https://api.github.com/repos/hogelog/test-repo/merges",
138 | "archive_url": "https://api.github.com/repos/hogelog/test-repo/{archive_format}{/ref}",
139 | "downloads_url": "https://api.github.com/repos/hogelog/test-repo/downloads",
140 | "issues_url": "https://api.github.com/repos/hogelog/test-repo/issues{/number}",
141 | "pulls_url": "https://api.github.com/repos/hogelog/test-repo/pulls{/number}",
142 | "milestones_url": "https://api.github.com/repos/hogelog/test-repo/milestones{/number}",
143 | "notifications_url": "https://api.github.com/repos/hogelog/test-repo/notifications{?since,all,participating}",
144 | "labels_url": "https://api.github.com/repos/hogelog/test-repo/labels{/name}",
145 | "releases_url": "https://api.github.com/repos/hogelog/test-repo/releases{/id}",
146 | "deployments_url": "https://api.github.com/repos/hogelog/test-repo/deployments",
147 | "created_at": "2017-06-01T13:26:00Z",
148 | "updated_at": "2017-06-01T14:03:40Z",
149 | "pushed_at": "2017-06-01T15:26:59Z",
150 | "git_url": "git://github.com/hogelog/test-repo.git",
151 | "ssh_url": "git@github.com:hogelog/test-repo.git",
152 | "clone_url": "https://github.com/hogelog/test-repo.git",
153 | "svn_url": "https://github.com/hogelog/test-repo",
154 | "homepage": null,
155 | "size": 1,
156 | "stargazers_count": 0,
157 | "watchers_count": 0,
158 | "language": "Ruby",
159 | "has_issues": true,
160 | "has_projects": true,
161 | "has_downloads": true,
162 | "has_wiki": true,
163 | "has_pages": false,
164 | "forks_count": 0,
165 | "mirror_url": null,
166 | "open_issues_count": 1,
167 | "forks": 0,
168 | "open_issues": 1,
169 | "watchers": 0,
170 | "default_branch": "master"
171 | },
172 | "sender": {
173 | "login": "hogelog",
174 | "id": 50920,
175 | "avatar_url": "https://avatars2.githubusercontent.com/u/50920?v=3",
176 | "gravatar_id": "",
177 | "url": "https://api.github.com/users/hogelog",
178 | "html_url": "https://github.com/hogelog",
179 | "followers_url": "https://api.github.com/users/hogelog/followers",
180 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
181 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
182 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
183 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
184 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
185 | "repos_url": "https://api.github.com/users/hogelog/repos",
186 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
187 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
188 | "type": "User",
189 | "site_admin": false
190 | }
191 | }
--------------------------------------------------------------------------------
/spec/payloads/pull_request.review_requested.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "review_requested",
3 | "number": 2,
4 | "pull_request": {
5 | "url": "https://api.github.com/repos/hogelog/test-repo/pulls/2",
6 | "id": 123527521,
7 | "html_url": "https://github.com/hogelog/test-repo/pull/2",
8 | "diff_url": "https://github.com/hogelog/test-repo/pull/2.diff",
9 | "patch_url": "https://github.com/hogelog/test-repo/pull/2.patch",
10 | "issue_url": "https://api.github.com/repos/hogelog/test-repo/issues/2",
11 | "number": 2,
12 | "state": "open",
13 | "locked": false,
14 | "title": "Super great feature",
15 | "user": {
16 | "login": "hogelog",
17 | "id": 50920,
18 | "avatar_url": "https://avatars2.githubusercontent.com/u/50920?v=3",
19 | "gravatar_id": "",
20 | "url": "https://api.github.com/users/hogelog",
21 | "html_url": "https://github.com/hogelog",
22 | "followers_url": "https://api.github.com/users/hogelog/followers",
23 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
24 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
25 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
26 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
27 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
28 | "repos_url": "https://api.github.com/users/hogelog/repos",
29 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
30 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
31 | "type": "User",
32 | "site_admin": false
33 | },
34 | "body": "I implemented super great feature.",
35 | "created_at": "2017-06-01T15:26:58Z",
36 | "updated_at": "2017-06-01T15:26:58Z",
37 | "closed_at": null,
38 | "merged_at": null,
39 | "merge_commit_sha": null,
40 | "assignee": null,
41 | "assignees": [
42 |
43 | ],
44 | "requested_reviewers": [
45 |
46 | ],
47 | "requested_teams": [
48 |
49 | ],
50 | "labels": [
51 | ],
52 | "milestone": null,
53 | "commits_url": "https://api.github.com/repos/hogelog/test-repo/pulls/2/commits",
54 | "review_comments_url": "https://api.github.com/repos/hogelog/test-repo/pulls/2/comments",
55 | "review_comment_url": "https://api.github.com/repos/hogelog/test-repo/pulls/comments{/number}",
56 | "comments_url": "https://api.github.com/repos/hogelog/test-repo/issues/2/comments",
57 | "statuses_url": "https://api.github.com/repos/hogelog/test-repo/statuses/887720a39cd6a5348898429a5d110cd90f26ca10",
58 | "head": {
59 | "label": "hogelog:fuga",
60 | "ref": "fuga",
61 | "sha": "887720a39cd6a5348898429a5d110cd90f26ca10",
62 | "user": {
63 | "login": "hogelog",
64 | "id": 50920,
65 | "avatar_url": "https://avatars2.githubusercontent.com/u/50920?v=3",
66 | "gravatar_id": "",
67 | "url": "https://api.github.com/users/hogelog",
68 | "html_url": "https://github.com/hogelog",
69 | "followers_url": "https://api.github.com/users/hogelog/followers",
70 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
71 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
72 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
73 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
74 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
75 | "repos_url": "https://api.github.com/users/hogelog/repos",
76 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
77 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
78 | "type": "User",
79 | "site_admin": false
80 | },
81 | "repo": {
82 | "id": 93059937,
83 | "name": "test-repo",
84 | "full_name": "hogelog/test-repo",
85 | "owner": {
86 | "login": "hogelog",
87 | "id": 50920,
88 | "avatar_url": "https://avatars2.githubusercontent.com/u/50920?v=3",
89 | "gravatar_id": "",
90 | "url": "https://api.github.com/users/hogelog",
91 | "html_url": "https://github.com/hogelog",
92 | "followers_url": "https://api.github.com/users/hogelog/followers",
93 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
94 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
95 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
96 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
97 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
98 | "repos_url": "https://api.github.com/users/hogelog/repos",
99 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
100 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
101 | "type": "User",
102 | "site_admin": false
103 | },
104 | "private": false,
105 | "html_url": "https://github.com/hogelog/test-repo",
106 | "description": null,
107 | "fork": false,
108 | "url": "https://api.github.com/repos/hogelog/test-repo",
109 | "forks_url": "https://api.github.com/repos/hogelog/test-repo/forks",
110 | "keys_url": "https://api.github.com/repos/hogelog/test-repo/keys{/key_id}",
111 | "collaborators_url": "https://api.github.com/repos/hogelog/test-repo/collaborators{/collaborator}",
112 | "teams_url": "https://api.github.com/repos/hogelog/test-repo/teams",
113 | "hooks_url": "https://api.github.com/repos/hogelog/test-repo/hooks",
114 | "issue_events_url": "https://api.github.com/repos/hogelog/test-repo/issues/events{/number}",
115 | "events_url": "https://api.github.com/repos/hogelog/test-repo/events",
116 | "assignees_url": "https://api.github.com/repos/hogelog/test-repo/assignees{/user}",
117 | "branches_url": "https://api.github.com/repos/hogelog/test-repo/branches{/branch}",
118 | "tags_url": "https://api.github.com/repos/hogelog/test-repo/tags",
119 | "blobs_url": "https://api.github.com/repos/hogelog/test-repo/git/blobs{/sha}",
120 | "git_tags_url": "https://api.github.com/repos/hogelog/test-repo/git/tags{/sha}",
121 | "git_refs_url": "https://api.github.com/repos/hogelog/test-repo/git/refs{/sha}",
122 | "trees_url": "https://api.github.com/repos/hogelog/test-repo/git/trees{/sha}",
123 | "statuses_url": "https://api.github.com/repos/hogelog/test-repo/statuses/{sha}",
124 | "languages_url": "https://api.github.com/repos/hogelog/test-repo/languages",
125 | "stargazers_url": "https://api.github.com/repos/hogelog/test-repo/stargazers",
126 | "contributors_url": "https://api.github.com/repos/hogelog/test-repo/contributors",
127 | "subscribers_url": "https://api.github.com/repos/hogelog/test-repo/subscribers",
128 | "subscription_url": "https://api.github.com/repos/hogelog/test-repo/subscription",
129 | "commits_url": "https://api.github.com/repos/hogelog/test-repo/commits{/sha}",
130 | "git_commits_url": "https://api.github.com/repos/hogelog/test-repo/git/commits{/sha}",
131 | "comments_url": "https://api.github.com/repos/hogelog/test-repo/comments{/number}",
132 | "issue_comment_url": "https://api.github.com/repos/hogelog/test-repo/issues/comments{/number}",
133 | "contents_url": "https://api.github.com/repos/hogelog/test-repo/contents/{+path}",
134 | "compare_url": "https://api.github.com/repos/hogelog/test-repo/compare/{base}...{head}",
135 | "merges_url": "https://api.github.com/repos/hogelog/test-repo/merges",
136 | "archive_url": "https://api.github.com/repos/hogelog/test-repo/{archive_format}{/ref}",
137 | "downloads_url": "https://api.github.com/repos/hogelog/test-repo/downloads",
138 | "issues_url": "https://api.github.com/repos/hogelog/test-repo/issues{/number}",
139 | "pulls_url": "https://api.github.com/repos/hogelog/test-repo/pulls{/number}",
140 | "milestones_url": "https://api.github.com/repos/hogelog/test-repo/milestones{/number}",
141 | "notifications_url": "https://api.github.com/repos/hogelog/test-repo/notifications{?since,all,participating}",
142 | "labels_url": "https://api.github.com/repos/hogelog/test-repo/labels{/name}",
143 | "releases_url": "https://api.github.com/repos/hogelog/test-repo/releases{/id}",
144 | "deployments_url": "https://api.github.com/repos/hogelog/test-repo/deployments",
145 | "created_at": "2017-06-01T13:26:00Z",
146 | "updated_at": "2017-06-01T14:03:40Z",
147 | "pushed_at": "2017-06-01T15:26:35Z",
148 | "git_url": "git://github.com/hogelog/test-repo.git",
149 | "ssh_url": "git@github.com:hogelog/test-repo.git",
150 | "clone_url": "https://github.com/hogelog/test-repo.git",
151 | "svn_url": "https://github.com/hogelog/test-repo",
152 | "homepage": null,
153 | "size": 0,
154 | "stargazers_count": 0,
155 | "watchers_count": 0,
156 | "language": "Ruby",
157 | "has_issues": true,
158 | "has_projects": true,
159 | "has_downloads": true,
160 | "has_wiki": true,
161 | "has_pages": false,
162 | "forks_count": 0,
163 | "mirror_url": null,
164 | "open_issues_count": 1,
165 | "forks": 0,
166 | "open_issues": 1,
167 | "watchers": 0,
168 | "default_branch": "master"
169 | }
170 | },
171 | "base": {
172 | "label": "hogelog:master",
173 | "ref": "master",
174 | "sha": "e75b5b1c499aebaa17f125089aef5e50e6f3a1a3",
175 | "user": {
176 | "login": "hogelog",
177 | "id": 50920,
178 | "avatar_url": "https://avatars2.githubusercontent.com/u/50920?v=3",
179 | "gravatar_id": "",
180 | "url": "https://api.github.com/users/hogelog",
181 | "html_url": "https://github.com/hogelog",
182 | "followers_url": "https://api.github.com/users/hogelog/followers",
183 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
184 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
185 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
186 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
187 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
188 | "repos_url": "https://api.github.com/users/hogelog/repos",
189 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
190 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
191 | "type": "User",
192 | "site_admin": false
193 | },
194 | "repo": {
195 | "id": 93059937,
196 | "name": "test-repo",
197 | "full_name": "hogelog/test-repo",
198 | "owner": {
199 | "login": "hogelog",
200 | "id": 50920,
201 | "avatar_url": "https://avatars2.githubusercontent.com/u/50920?v=3",
202 | "gravatar_id": "",
203 | "url": "https://api.github.com/users/hogelog",
204 | "html_url": "https://github.com/hogelog",
205 | "followers_url": "https://api.github.com/users/hogelog/followers",
206 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
207 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
208 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
209 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
210 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
211 | "repos_url": "https://api.github.com/users/hogelog/repos",
212 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
213 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
214 | "type": "User",
215 | "site_admin": false
216 | },
217 | "private": false,
218 | "html_url": "https://github.com/hogelog/test-repo",
219 | "description": null,
220 | "fork": false,
221 | "url": "https://api.github.com/repos/hogelog/test-repo",
222 | "forks_url": "https://api.github.com/repos/hogelog/test-repo/forks",
223 | "keys_url": "https://api.github.com/repos/hogelog/test-repo/keys{/key_id}",
224 | "collaborators_url": "https://api.github.com/repos/hogelog/test-repo/collaborators{/collaborator}",
225 | "teams_url": "https://api.github.com/repos/hogelog/test-repo/teams",
226 | "hooks_url": "https://api.github.com/repos/hogelog/test-repo/hooks",
227 | "issue_events_url": "https://api.github.com/repos/hogelog/test-repo/issues/events{/number}",
228 | "events_url": "https://api.github.com/repos/hogelog/test-repo/events",
229 | "assignees_url": "https://api.github.com/repos/hogelog/test-repo/assignees{/user}",
230 | "branches_url": "https://api.github.com/repos/hogelog/test-repo/branches{/branch}",
231 | "tags_url": "https://api.github.com/repos/hogelog/test-repo/tags",
232 | "blobs_url": "https://api.github.com/repos/hogelog/test-repo/git/blobs{/sha}",
233 | "git_tags_url": "https://api.github.com/repos/hogelog/test-repo/git/tags{/sha}",
234 | "git_refs_url": "https://api.github.com/repos/hogelog/test-repo/git/refs{/sha}",
235 | "trees_url": "https://api.github.com/repos/hogelog/test-repo/git/trees{/sha}",
236 | "statuses_url": "https://api.github.com/repos/hogelog/test-repo/statuses/{sha}",
237 | "languages_url": "https://api.github.com/repos/hogelog/test-repo/languages",
238 | "stargazers_url": "https://api.github.com/repos/hogelog/test-repo/stargazers",
239 | "contributors_url": "https://api.github.com/repos/hogelog/test-repo/contributors",
240 | "subscribers_url": "https://api.github.com/repos/hogelog/test-repo/subscribers",
241 | "subscription_url": "https://api.github.com/repos/hogelog/test-repo/subscription",
242 | "commits_url": "https://api.github.com/repos/hogelog/test-repo/commits{/sha}",
243 | "git_commits_url": "https://api.github.com/repos/hogelog/test-repo/git/commits{/sha}",
244 | "comments_url": "https://api.github.com/repos/hogelog/test-repo/comments{/number}",
245 | "issue_comment_url": "https://api.github.com/repos/hogelog/test-repo/issues/comments{/number}",
246 | "contents_url": "https://api.github.com/repos/hogelog/test-repo/contents/{+path}",
247 | "compare_url": "https://api.github.com/repos/hogelog/test-repo/compare/{base}...{head}",
248 | "merges_url": "https://api.github.com/repos/hogelog/test-repo/merges",
249 | "archive_url": "https://api.github.com/repos/hogelog/test-repo/{archive_format}{/ref}",
250 | "downloads_url": "https://api.github.com/repos/hogelog/test-repo/downloads",
251 | "issues_url": "https://api.github.com/repos/hogelog/test-repo/issues{/number}",
252 | "pulls_url": "https://api.github.com/repos/hogelog/test-repo/pulls{/number}",
253 | "milestones_url": "https://api.github.com/repos/hogelog/test-repo/milestones{/number}",
254 | "notifications_url": "https://api.github.com/repos/hogelog/test-repo/notifications{?since,all,participating}",
255 | "labels_url": "https://api.github.com/repos/hogelog/test-repo/labels{/name}",
256 | "releases_url": "https://api.github.com/repos/hogelog/test-repo/releases{/id}",
257 | "deployments_url": "https://api.github.com/repos/hogelog/test-repo/deployments",
258 | "created_at": "2017-06-01T13:26:00Z",
259 | "updated_at": "2017-06-01T14:03:40Z",
260 | "pushed_at": "2017-06-01T15:26:35Z",
261 | "git_url": "git://github.com/hogelog/test-repo.git",
262 | "ssh_url": "git@github.com:hogelog/test-repo.git",
263 | "clone_url": "https://github.com/hogelog/test-repo.git",
264 | "svn_url": "https://github.com/hogelog/test-repo",
265 | "homepage": null,
266 | "size": 0,
267 | "stargazers_count": 0,
268 | "watchers_count": 0,
269 | "language": "Ruby",
270 | "has_issues": true,
271 | "has_projects": true,
272 | "has_downloads": true,
273 | "has_wiki": true,
274 | "has_pages": false,
275 | "forks_count": 0,
276 | "mirror_url": null,
277 | "open_issues_count": 1,
278 | "forks": 0,
279 | "open_issues": 1,
280 | "watchers": 0,
281 | "default_branch": "master"
282 | }
283 | },
284 | "_links": {
285 | "self": {
286 | "href": "https://api.github.com/repos/hogelog/test-repo/pulls/2"
287 | },
288 | "html": {
289 | "href": "https://github.com/hogelog/test-repo/pull/2"
290 | },
291 | "issue": {
292 | "href": "https://api.github.com/repos/hogelog/test-repo/issues/2"
293 | },
294 | "comments": {
295 | "href": "https://api.github.com/repos/hogelog/test-repo/issues/2/comments"
296 | },
297 | "review_comments": {
298 | "href": "https://api.github.com/repos/hogelog/test-repo/pulls/2/comments"
299 | },
300 | "review_comment": {
301 | "href": "https://api.github.com/repos/hogelog/test-repo/pulls/comments{/number}"
302 | },
303 | "commits": {
304 | "href": "https://api.github.com/repos/hogelog/test-repo/pulls/2/commits"
305 | },
306 | "statuses": {
307 | "href": "https://api.github.com/repos/hogelog/test-repo/statuses/887720a39cd6a5348898429a5d110cd90f26ca10"
308 | }
309 | },
310 | "merged": false,
311 | "mergeable": null,
312 | "rebaseable": null,
313 | "mergeable_state": "unknown",
314 | "merged_by": null,
315 | "comments": 0,
316 | "review_comments": 0,
317 | "maintainer_can_modify": false,
318 | "commits": 1,
319 | "additions": 1,
320 | "deletions": 0,
321 | "changed_files": 1
322 | },
323 | "requested_reviewer": {
324 | "login": "other_user",
325 | "id": 1,
326 | "node_id": "MDQ6VXNlcjE=",
327 | "avatar_url": "https://github.com/images/error/other_user_happy.gif",
328 | "gravatar_id": "",
329 | "url": "https://api.github.com/users/other_user",
330 | "html_url": "https://github.com/other_user",
331 | "followers_url": "https://api.github.com/users/other_user/followers",
332 | "following_url": "https://api.github.com/users/other_user/following{/other_user}",
333 | "gists_url": "https://api.github.com/users/other_user/gists{/gist_id}",
334 | "starred_url": "https://api.github.com/users/other_user/starred{/owner}{/repo}",
335 | "subscriptions_url": "https://api.github.com/users/other_user/subscriptions",
336 | "organizations_url": "https://api.github.com/users/other_user/orgs",
337 | "repos_url": "https://api.github.com/users/other_user/repos",
338 | "events_url": "https://api.github.com/users/other_user/events{/privacy}",
339 | "received_events_url": "https://api.github.com/users/other_user/received_events",
340 | "type": "User",
341 | "site_admin": false
342 | },
343 | "repository": {
344 | "id": 93059937,
345 | "name": "test-repo",
346 | "full_name": "hogelog/test-repo",
347 | "owner": {
348 | "login": "hogelog",
349 | "id": 50920,
350 | "avatar_url": "https://avatars2.githubusercontent.com/u/50920?v=3",
351 | "gravatar_id": "",
352 | "url": "https://api.github.com/users/hogelog",
353 | "html_url": "https://github.com/hogelog",
354 | "followers_url": "https://api.github.com/users/hogelog/followers",
355 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
356 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
357 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
358 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
359 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
360 | "repos_url": "https://api.github.com/users/hogelog/repos",
361 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
362 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
363 | "type": "User",
364 | "site_admin": false
365 | },
366 | "private": false,
367 | "html_url": "https://github.com/hogelog/test-repo",
368 | "description": null,
369 | "fork": false,
370 | "url": "https://api.github.com/repos/hogelog/test-repo",
371 | "forks_url": "https://api.github.com/repos/hogelog/test-repo/forks",
372 | "keys_url": "https://api.github.com/repos/hogelog/test-repo/keys{/key_id}",
373 | "collaborators_url": "https://api.github.com/repos/hogelog/test-repo/collaborators{/collaborator}",
374 | "teams_url": "https://api.github.com/repos/hogelog/test-repo/teams",
375 | "hooks_url": "https://api.github.com/repos/hogelog/test-repo/hooks",
376 | "issue_events_url": "https://api.github.com/repos/hogelog/test-repo/issues/events{/number}",
377 | "events_url": "https://api.github.com/repos/hogelog/test-repo/events",
378 | "assignees_url": "https://api.github.com/repos/hogelog/test-repo/assignees{/user}",
379 | "branches_url": "https://api.github.com/repos/hogelog/test-repo/branches{/branch}",
380 | "tags_url": "https://api.github.com/repos/hogelog/test-repo/tags",
381 | "blobs_url": "https://api.github.com/repos/hogelog/test-repo/git/blobs{/sha}",
382 | "git_tags_url": "https://api.github.com/repos/hogelog/test-repo/git/tags{/sha}",
383 | "git_refs_url": "https://api.github.com/repos/hogelog/test-repo/git/refs{/sha}",
384 | "trees_url": "https://api.github.com/repos/hogelog/test-repo/git/trees{/sha}",
385 | "statuses_url": "https://api.github.com/repos/hogelog/test-repo/statuses/{sha}",
386 | "languages_url": "https://api.github.com/repos/hogelog/test-repo/languages",
387 | "stargazers_url": "https://api.github.com/repos/hogelog/test-repo/stargazers",
388 | "contributors_url": "https://api.github.com/repos/hogelog/test-repo/contributors",
389 | "subscribers_url": "https://api.github.com/repos/hogelog/test-repo/subscribers",
390 | "subscription_url": "https://api.github.com/repos/hogelog/test-repo/subscription",
391 | "commits_url": "https://api.github.com/repos/hogelog/test-repo/commits{/sha}",
392 | "git_commits_url": "https://api.github.com/repos/hogelog/test-repo/git/commits{/sha}",
393 | "comments_url": "https://api.github.com/repos/hogelog/test-repo/comments{/number}",
394 | "issue_comment_url": "https://api.github.com/repos/hogelog/test-repo/issues/comments{/number}",
395 | "contents_url": "https://api.github.com/repos/hogelog/test-repo/contents/{+path}",
396 | "compare_url": "https://api.github.com/repos/hogelog/test-repo/compare/{base}...{head}",
397 | "merges_url": "https://api.github.com/repos/hogelog/test-repo/merges",
398 | "archive_url": "https://api.github.com/repos/hogelog/test-repo/{archive_format}{/ref}",
399 | "downloads_url": "https://api.github.com/repos/hogelog/test-repo/downloads",
400 | "issues_url": "https://api.github.com/repos/hogelog/test-repo/issues{/number}",
401 | "pulls_url": "https://api.github.com/repos/hogelog/test-repo/pulls{/number}",
402 | "milestones_url": "https://api.github.com/repos/hogelog/test-repo/milestones{/number}",
403 | "notifications_url": "https://api.github.com/repos/hogelog/test-repo/notifications{?since,all,participating}",
404 | "labels_url": "https://api.github.com/repos/hogelog/test-repo/labels{/name}",
405 | "releases_url": "https://api.github.com/repos/hogelog/test-repo/releases{/id}",
406 | "deployments_url": "https://api.github.com/repos/hogelog/test-repo/deployments",
407 | "created_at": "2017-06-01T13:26:00Z",
408 | "updated_at": "2017-06-01T14:03:40Z",
409 | "pushed_at": "2017-06-01T15:26:35Z",
410 | "git_url": "git://github.com/hogelog/test-repo.git",
411 | "ssh_url": "git@github.com:hogelog/test-repo.git",
412 | "clone_url": "https://github.com/hogelog/test-repo.git",
413 | "svn_url": "https://github.com/hogelog/test-repo",
414 | "homepage": null,
415 | "size": 0,
416 | "stargazers_count": 0,
417 | "watchers_count": 0,
418 | "language": "Ruby",
419 | "has_issues": true,
420 | "has_projects": true,
421 | "has_downloads": true,
422 | "has_wiki": true,
423 | "has_pages": false,
424 | "forks_count": 0,
425 | "mirror_url": null,
426 | "open_issues_count": 1,
427 | "forks": 0,
428 | "open_issues": 1,
429 | "watchers": 0,
430 | "default_branch": "master"
431 | },
432 | "sender": {
433 | "login": "hogelog",
434 | "id": 50920,
435 | "avatar_url": "https://avatars2.githubusercontent.com/u/50920?v=3",
436 | "gravatar_id": "",
437 | "url": "https://api.github.com/users/hogelog",
438 | "html_url": "https://github.com/hogelog",
439 | "followers_url": "https://api.github.com/users/hogelog/followers",
440 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
441 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
442 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
443 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
444 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
445 | "repos_url": "https://api.github.com/users/hogelog/repos",
446 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
447 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
448 | "type": "User",
449 | "site_admin": false
450 | }
451 | }
452 |
--------------------------------------------------------------------------------
/spec/payloads/pull_request_review.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "submitted",
3 | "review": {
4 | "id": 57343613,
5 | "user": {
6 | "login": "hogelog",
7 | "id": 50920,
8 | "avatar_url": "https://avatars1.githubusercontent.com/u/50920?v=4",
9 | "gravatar_id": "",
10 | "url": "https://api.github.com/users/hogelog",
11 | "html_url": "https://github.com/hogelog",
12 | "followers_url": "https://api.github.com/users/hogelog/followers",
13 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
14 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
15 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
16 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
17 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
18 | "repos_url": "https://api.github.com/users/hogelog/repos",
19 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
20 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
21 | "type": "User",
22 | "site_admin": false
23 | },
24 | "body": "Hi",
25 | "commit_id": "5ed03c5fb86d88994fda89c656cc4c9c45d0d317",
26 | "submitted_at": "2017-08-19T06:45:02Z",
27 | "state": "commented",
28 | "html_url": "https://github.com/hogelog/test-repo/pull/6#pullrequestreview-57343613",
29 | "pull_request_url": "https://api.github.com/repos/hogelog/test-repo/pulls/6",
30 | "_links": {
31 | "html": {
32 | "href": "https://github.com/hogelog/test-repo/pull/6#pullrequestreview-57343613"
33 | },
34 | "pull_request": {
35 | "href": "https://api.github.com/repos/hogelog/test-repo/pulls/6"
36 | }
37 | }
38 | },
39 | "pull_request": {
40 | "url": "https://api.github.com/repos/hogelog/test-repo/pulls/6",
41 | "id": 123948075,
42 | "html_url": "https://github.com/hogelog/test-repo/pull/6",
43 | "diff_url": "https://github.com/hogelog/test-repo/pull/6.diff",
44 | "patch_url": "https://github.com/hogelog/test-repo/pull/6.patch",
45 | "issue_url": "https://api.github.com/repos/hogelog/test-repo/issues/6",
46 | "number": 6,
47 | "state": "open",
48 | "locked": false,
49 | "title": "Moge",
50 | "user": {
51 | "login": "hogelog",
52 | "id": 50920,
53 | "avatar_url": "https://avatars1.githubusercontent.com/u/50920?v=4",
54 | "gravatar_id": "",
55 | "url": "https://api.github.com/users/hogelog",
56 | "html_url": "https://github.com/hogelog",
57 | "followers_url": "https://api.github.com/users/hogelog/followers",
58 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
59 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
60 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
61 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
62 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
63 | "repos_url": "https://api.github.com/users/hogelog/repos",
64 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
65 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
66 | "type": "User",
67 | "site_admin": false
68 | },
69 | "body": "aaaaaaaa\r\nbbb",
70 | "created_at": "2017-06-05T10:03:09Z",
71 | "updated_at": "2017-08-19T06:45:02Z",
72 | "closed_at": null,
73 | "merged_at": null,
74 | "merge_commit_sha": "064210440b09c7d4cfb5bd7a301fe50127a86e6d",
75 | "assignee": null,
76 | "assignees": [
77 |
78 | ],
79 | "requested_reviewers": [
80 |
81 | ],
82 | "requested_teams": [
83 |
84 | ],
85 | "milestone": null,
86 | "commits_url": "https://api.github.com/repos/hogelog/test-repo/pulls/6/commits",
87 | "review_comments_url": "https://api.github.com/repos/hogelog/test-repo/pulls/6/comments",
88 | "review_comment_url": "https://api.github.com/repos/hogelog/test-repo/pulls/comments{/number}",
89 | "comments_url": "https://api.github.com/repos/hogelog/test-repo/issues/6/comments",
90 | "statuses_url": "https://api.github.com/repos/hogelog/test-repo/statuses/5ed03c5fb86d88994fda89c656cc4c9c45d0d317",
91 | "head": {
92 | "label": "hogelog:moge",
93 | "ref": "moge",
94 | "sha": "5ed03c5fb86d88994fda89c656cc4c9c45d0d317",
95 | "user": {
96 | "login": "hogelog",
97 | "id": 50920,
98 | "avatar_url": "https://avatars1.githubusercontent.com/u/50920?v=4",
99 | "gravatar_id": "",
100 | "url": "https://api.github.com/users/hogelog",
101 | "html_url": "https://github.com/hogelog",
102 | "followers_url": "https://api.github.com/users/hogelog/followers",
103 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
104 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
105 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
106 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
107 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
108 | "repos_url": "https://api.github.com/users/hogelog/repos",
109 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
110 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
111 | "type": "User",
112 | "site_admin": false
113 | },
114 | "repo": {
115 | "id": 93059937,
116 | "name": "test-repo",
117 | "full_name": "hogelog/test-repo",
118 | "owner": {
119 | "login": "hogelog",
120 | "id": 50920,
121 | "avatar_url": "https://avatars1.githubusercontent.com/u/50920?v=4",
122 | "gravatar_id": "",
123 | "url": "https://api.github.com/users/hogelog",
124 | "html_url": "https://github.com/hogelog",
125 | "followers_url": "https://api.github.com/users/hogelog/followers",
126 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
127 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
128 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
129 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
130 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
131 | "repos_url": "https://api.github.com/users/hogelog/repos",
132 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
133 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
134 | "type": "User",
135 | "site_admin": false
136 | },
137 | "private": false,
138 | "html_url": "https://github.com/hogelog/test-repo",
139 | "description": null,
140 | "fork": false,
141 | "url": "https://api.github.com/repos/hogelog/test-repo",
142 | "forks_url": "https://api.github.com/repos/hogelog/test-repo/forks",
143 | "keys_url": "https://api.github.com/repos/hogelog/test-repo/keys{/key_id}",
144 | "collaborators_url": "https://api.github.com/repos/hogelog/test-repo/collaborators{/collaborator}",
145 | "teams_url": "https://api.github.com/repos/hogelog/test-repo/teams",
146 | "hooks_url": "https://api.github.com/repos/hogelog/test-repo/hooks",
147 | "issue_events_url": "https://api.github.com/repos/hogelog/test-repo/issues/events{/number}",
148 | "events_url": "https://api.github.com/repos/hogelog/test-repo/events",
149 | "assignees_url": "https://api.github.com/repos/hogelog/test-repo/assignees{/user}",
150 | "branches_url": "https://api.github.com/repos/hogelog/test-repo/branches{/branch}",
151 | "tags_url": "https://api.github.com/repos/hogelog/test-repo/tags",
152 | "blobs_url": "https://api.github.com/repos/hogelog/test-repo/git/blobs{/sha}",
153 | "git_tags_url": "https://api.github.com/repos/hogelog/test-repo/git/tags{/sha}",
154 | "git_refs_url": "https://api.github.com/repos/hogelog/test-repo/git/refs{/sha}",
155 | "trees_url": "https://api.github.com/repos/hogelog/test-repo/git/trees{/sha}",
156 | "statuses_url": "https://api.github.com/repos/hogelog/test-repo/statuses/{sha}",
157 | "languages_url": "https://api.github.com/repos/hogelog/test-repo/languages",
158 | "stargazers_url": "https://api.github.com/repos/hogelog/test-repo/stargazers",
159 | "contributors_url": "https://api.github.com/repos/hogelog/test-repo/contributors",
160 | "subscribers_url": "https://api.github.com/repos/hogelog/test-repo/subscribers",
161 | "subscription_url": "https://api.github.com/repos/hogelog/test-repo/subscription",
162 | "commits_url": "https://api.github.com/repos/hogelog/test-repo/commits{/sha}",
163 | "git_commits_url": "https://api.github.com/repos/hogelog/test-repo/git/commits{/sha}",
164 | "comments_url": "https://api.github.com/repos/hogelog/test-repo/comments{/number}",
165 | "issue_comment_url": "https://api.github.com/repos/hogelog/test-repo/issues/comments{/number}",
166 | "contents_url": "https://api.github.com/repos/hogelog/test-repo/contents/{+path}",
167 | "compare_url": "https://api.github.com/repos/hogelog/test-repo/compare/{base}...{head}",
168 | "merges_url": "https://api.github.com/repos/hogelog/test-repo/merges",
169 | "archive_url": "https://api.github.com/repos/hogelog/test-repo/{archive_format}{/ref}",
170 | "downloads_url": "https://api.github.com/repos/hogelog/test-repo/downloads",
171 | "issues_url": "https://api.github.com/repos/hogelog/test-repo/issues{/number}",
172 | "pulls_url": "https://api.github.com/repos/hogelog/test-repo/pulls{/number}",
173 | "milestones_url": "https://api.github.com/repos/hogelog/test-repo/milestones{/number}",
174 | "notifications_url": "https://api.github.com/repos/hogelog/test-repo/notifications{?since,all,participating}",
175 | "labels_url": "https://api.github.com/repos/hogelog/test-repo/labels{/name}",
176 | "releases_url": "https://api.github.com/repos/hogelog/test-repo/releases{/id}",
177 | "deployments_url": "https://api.github.com/repos/hogelog/test-repo/deployments",
178 | "created_at": "2017-06-01T13:26:00Z",
179 | "updated_at": "2017-06-01T14:03:40Z",
180 | "pushed_at": "2017-06-05T14:35:20Z",
181 | "git_url": "git://github.com/hogelog/test-repo.git",
182 | "ssh_url": "git@github.com:hogelog/test-repo.git",
183 | "clone_url": "https://github.com/hogelog/test-repo.git",
184 | "svn_url": "https://github.com/hogelog/test-repo",
185 | "homepage": null,
186 | "size": 1,
187 | "stargazers_count": 0,
188 | "watchers_count": 0,
189 | "language": "Ruby",
190 | "has_issues": true,
191 | "has_projects": true,
192 | "has_downloads": true,
193 | "has_wiki": true,
194 | "has_pages": false,
195 | "forks_count": 0,
196 | "mirror_url": null,
197 | "open_issues_count": 4,
198 | "forks": 0,
199 | "open_issues": 4,
200 | "watchers": 0,
201 | "default_branch": "master"
202 | }
203 | },
204 | "base": {
205 | "label": "hogelog:master",
206 | "ref": "master",
207 | "sha": "e75b5b1c499aebaa17f125089aef5e50e6f3a1a3",
208 | "user": {
209 | "login": "hogelog",
210 | "id": 50920,
211 | "avatar_url": "https://avatars1.githubusercontent.com/u/50920?v=4",
212 | "gravatar_id": "",
213 | "url": "https://api.github.com/users/hogelog",
214 | "html_url": "https://github.com/hogelog",
215 | "followers_url": "https://api.github.com/users/hogelog/followers",
216 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
217 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
218 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
219 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
220 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
221 | "repos_url": "https://api.github.com/users/hogelog/repos",
222 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
223 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
224 | "type": "User",
225 | "site_admin": false
226 | },
227 | "repo": {
228 | "id": 93059937,
229 | "name": "test-repo",
230 | "full_name": "hogelog/test-repo",
231 | "owner": {
232 | "login": "hogelog",
233 | "id": 50920,
234 | "avatar_url": "https://avatars1.githubusercontent.com/u/50920?v=4",
235 | "gravatar_id": "",
236 | "url": "https://api.github.com/users/hogelog",
237 | "html_url": "https://github.com/hogelog",
238 | "followers_url": "https://api.github.com/users/hogelog/followers",
239 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
240 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
241 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
242 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
243 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
244 | "repos_url": "https://api.github.com/users/hogelog/repos",
245 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
246 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
247 | "type": "User",
248 | "site_admin": false
249 | },
250 | "private": false,
251 | "html_url": "https://github.com/hogelog/test-repo",
252 | "description": null,
253 | "fork": false,
254 | "url": "https://api.github.com/repos/hogelog/test-repo",
255 | "forks_url": "https://api.github.com/repos/hogelog/test-repo/forks",
256 | "keys_url": "https://api.github.com/repos/hogelog/test-repo/keys{/key_id}",
257 | "collaborators_url": "https://api.github.com/repos/hogelog/test-repo/collaborators{/collaborator}",
258 | "teams_url": "https://api.github.com/repos/hogelog/test-repo/teams",
259 | "hooks_url": "https://api.github.com/repos/hogelog/test-repo/hooks",
260 | "issue_events_url": "https://api.github.com/repos/hogelog/test-repo/issues/events{/number}",
261 | "events_url": "https://api.github.com/repos/hogelog/test-repo/events",
262 | "assignees_url": "https://api.github.com/repos/hogelog/test-repo/assignees{/user}",
263 | "branches_url": "https://api.github.com/repos/hogelog/test-repo/branches{/branch}",
264 | "tags_url": "https://api.github.com/repos/hogelog/test-repo/tags",
265 | "blobs_url": "https://api.github.com/repos/hogelog/test-repo/git/blobs{/sha}",
266 | "git_tags_url": "https://api.github.com/repos/hogelog/test-repo/git/tags{/sha}",
267 | "git_refs_url": "https://api.github.com/repos/hogelog/test-repo/git/refs{/sha}",
268 | "trees_url": "https://api.github.com/repos/hogelog/test-repo/git/trees{/sha}",
269 | "statuses_url": "https://api.github.com/repos/hogelog/test-repo/statuses/{sha}",
270 | "languages_url": "https://api.github.com/repos/hogelog/test-repo/languages",
271 | "stargazers_url": "https://api.github.com/repos/hogelog/test-repo/stargazers",
272 | "contributors_url": "https://api.github.com/repos/hogelog/test-repo/contributors",
273 | "subscribers_url": "https://api.github.com/repos/hogelog/test-repo/subscribers",
274 | "subscription_url": "https://api.github.com/repos/hogelog/test-repo/subscription",
275 | "commits_url": "https://api.github.com/repos/hogelog/test-repo/commits{/sha}",
276 | "git_commits_url": "https://api.github.com/repos/hogelog/test-repo/git/commits{/sha}",
277 | "comments_url": "https://api.github.com/repos/hogelog/test-repo/comments{/number}",
278 | "issue_comment_url": "https://api.github.com/repos/hogelog/test-repo/issues/comments{/number}",
279 | "contents_url": "https://api.github.com/repos/hogelog/test-repo/contents/{+path}",
280 | "compare_url": "https://api.github.com/repos/hogelog/test-repo/compare/{base}...{head}",
281 | "merges_url": "https://api.github.com/repos/hogelog/test-repo/merges",
282 | "archive_url": "https://api.github.com/repos/hogelog/test-repo/{archive_format}{/ref}",
283 | "downloads_url": "https://api.github.com/repos/hogelog/test-repo/downloads",
284 | "issues_url": "https://api.github.com/repos/hogelog/test-repo/issues{/number}",
285 | "pulls_url": "https://api.github.com/repos/hogelog/test-repo/pulls{/number}",
286 | "milestones_url": "https://api.github.com/repos/hogelog/test-repo/milestones{/number}",
287 | "notifications_url": "https://api.github.com/repos/hogelog/test-repo/notifications{?since,all,participating}",
288 | "labels_url": "https://api.github.com/repos/hogelog/test-repo/labels{/name}",
289 | "releases_url": "https://api.github.com/repos/hogelog/test-repo/releases{/id}",
290 | "deployments_url": "https://api.github.com/repos/hogelog/test-repo/deployments",
291 | "created_at": "2017-06-01T13:26:00Z",
292 | "updated_at": "2017-06-01T14:03:40Z",
293 | "pushed_at": "2017-06-05T14:35:20Z",
294 | "git_url": "git://github.com/hogelog/test-repo.git",
295 | "ssh_url": "git@github.com:hogelog/test-repo.git",
296 | "clone_url": "https://github.com/hogelog/test-repo.git",
297 | "svn_url": "https://github.com/hogelog/test-repo",
298 | "homepage": null,
299 | "size": 1,
300 | "stargazers_count": 0,
301 | "watchers_count": 0,
302 | "language": "Ruby",
303 | "has_issues": true,
304 | "has_projects": true,
305 | "has_downloads": true,
306 | "has_wiki": true,
307 | "has_pages": false,
308 | "forks_count": 0,
309 | "mirror_url": null,
310 | "open_issues_count": 4,
311 | "forks": 0,
312 | "open_issues": 4,
313 | "watchers": 0,
314 | "default_branch": "master"
315 | }
316 | },
317 | "_links": {
318 | "self": {
319 | "href": "https://api.github.com/repos/hogelog/test-repo/pulls/6"
320 | },
321 | "html": {
322 | "href": "https://github.com/hogelog/test-repo/pull/6"
323 | },
324 | "issue": {
325 | "href": "https://api.github.com/repos/hogelog/test-repo/issues/6"
326 | },
327 | "comments": {
328 | "href": "https://api.github.com/repos/hogelog/test-repo/issues/6/comments"
329 | },
330 | "review_comments": {
331 | "href": "https://api.github.com/repos/hogelog/test-repo/pulls/6/comments"
332 | },
333 | "review_comment": {
334 | "href": "https://api.github.com/repos/hogelog/test-repo/pulls/comments{/number}"
335 | },
336 | "commits": {
337 | "href": "https://api.github.com/repos/hogelog/test-repo/pulls/6/commits"
338 | },
339 | "statuses": {
340 | "href": "https://api.github.com/repos/hogelog/test-repo/statuses/5ed03c5fb86d88994fda89c656cc4c9c45d0d317"
341 | }
342 | }
343 | },
344 | "repository": {
345 | "id": 93059937,
346 | "name": "test-repo",
347 | "full_name": "hogelog/test-repo",
348 | "owner": {
349 | "login": "hogelog",
350 | "id": 50920,
351 | "avatar_url": "https://avatars1.githubusercontent.com/u/50920?v=4",
352 | "gravatar_id": "",
353 | "url": "https://api.github.com/users/hogelog",
354 | "html_url": "https://github.com/hogelog",
355 | "followers_url": "https://api.github.com/users/hogelog/followers",
356 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
357 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
358 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
359 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
360 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
361 | "repos_url": "https://api.github.com/users/hogelog/repos",
362 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
363 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
364 | "type": "User",
365 | "site_admin": false
366 | },
367 | "private": false,
368 | "html_url": "https://github.com/hogelog/test-repo",
369 | "description": null,
370 | "fork": false,
371 | "url": "https://api.github.com/repos/hogelog/test-repo",
372 | "forks_url": "https://api.github.com/repos/hogelog/test-repo/forks",
373 | "keys_url": "https://api.github.com/repos/hogelog/test-repo/keys{/key_id}",
374 | "collaborators_url": "https://api.github.com/repos/hogelog/test-repo/collaborators{/collaborator}",
375 | "teams_url": "https://api.github.com/repos/hogelog/test-repo/teams",
376 | "hooks_url": "https://api.github.com/repos/hogelog/test-repo/hooks",
377 | "issue_events_url": "https://api.github.com/repos/hogelog/test-repo/issues/events{/number}",
378 | "events_url": "https://api.github.com/repos/hogelog/test-repo/events",
379 | "assignees_url": "https://api.github.com/repos/hogelog/test-repo/assignees{/user}",
380 | "branches_url": "https://api.github.com/repos/hogelog/test-repo/branches{/branch}",
381 | "tags_url": "https://api.github.com/repos/hogelog/test-repo/tags",
382 | "blobs_url": "https://api.github.com/repos/hogelog/test-repo/git/blobs{/sha}",
383 | "git_tags_url": "https://api.github.com/repos/hogelog/test-repo/git/tags{/sha}",
384 | "git_refs_url": "https://api.github.com/repos/hogelog/test-repo/git/refs{/sha}",
385 | "trees_url": "https://api.github.com/repos/hogelog/test-repo/git/trees{/sha}",
386 | "statuses_url": "https://api.github.com/repos/hogelog/test-repo/statuses/{sha}",
387 | "languages_url": "https://api.github.com/repos/hogelog/test-repo/languages",
388 | "stargazers_url": "https://api.github.com/repos/hogelog/test-repo/stargazers",
389 | "contributors_url": "https://api.github.com/repos/hogelog/test-repo/contributors",
390 | "subscribers_url": "https://api.github.com/repos/hogelog/test-repo/subscribers",
391 | "subscription_url": "https://api.github.com/repos/hogelog/test-repo/subscription",
392 | "commits_url": "https://api.github.com/repos/hogelog/test-repo/commits{/sha}",
393 | "git_commits_url": "https://api.github.com/repos/hogelog/test-repo/git/commits{/sha}",
394 | "comments_url": "https://api.github.com/repos/hogelog/test-repo/comments{/number}",
395 | "issue_comment_url": "https://api.github.com/repos/hogelog/test-repo/issues/comments{/number}",
396 | "contents_url": "https://api.github.com/repos/hogelog/test-repo/contents/{+path}",
397 | "compare_url": "https://api.github.com/repos/hogelog/test-repo/compare/{base}...{head}",
398 | "merges_url": "https://api.github.com/repos/hogelog/test-repo/merges",
399 | "archive_url": "https://api.github.com/repos/hogelog/test-repo/{archive_format}{/ref}",
400 | "downloads_url": "https://api.github.com/repos/hogelog/test-repo/downloads",
401 | "issues_url": "https://api.github.com/repos/hogelog/test-repo/issues{/number}",
402 | "pulls_url": "https://api.github.com/repos/hogelog/test-repo/pulls{/number}",
403 | "milestones_url": "https://api.github.com/repos/hogelog/test-repo/milestones{/number}",
404 | "notifications_url": "https://api.github.com/repos/hogelog/test-repo/notifications{?since,all,participating}",
405 | "labels_url": "https://api.github.com/repos/hogelog/test-repo/labels{/name}",
406 | "releases_url": "https://api.github.com/repos/hogelog/test-repo/releases{/id}",
407 | "deployments_url": "https://api.github.com/repos/hogelog/test-repo/deployments",
408 | "created_at": "2017-06-01T13:26:00Z",
409 | "updated_at": "2017-06-01T14:03:40Z",
410 | "pushed_at": "2017-06-05T14:35:20Z",
411 | "git_url": "git://github.com/hogelog/test-repo.git",
412 | "ssh_url": "git@github.com:hogelog/test-repo.git",
413 | "clone_url": "https://github.com/hogelog/test-repo.git",
414 | "svn_url": "https://github.com/hogelog/test-repo",
415 | "homepage": null,
416 | "size": 1,
417 | "stargazers_count": 0,
418 | "watchers_count": 0,
419 | "language": "Ruby",
420 | "has_issues": true,
421 | "has_projects": true,
422 | "has_downloads": true,
423 | "has_wiki": true,
424 | "has_pages": false,
425 | "forks_count": 0,
426 | "mirror_url": null,
427 | "open_issues_count": 4,
428 | "forks": 0,
429 | "open_issues": 4,
430 | "watchers": 0,
431 | "default_branch": "master"
432 | },
433 | "sender": {
434 | "login": "hogelog",
435 | "id": 50920,
436 | "avatar_url": "https://avatars1.githubusercontent.com/u/50920?v=4",
437 | "gravatar_id": "",
438 | "url": "https://api.github.com/users/hogelog",
439 | "html_url": "https://github.com/hogelog",
440 | "followers_url": "https://api.github.com/users/hogelog/followers",
441 | "following_url": "https://api.github.com/users/hogelog/following{/other_user}",
442 | "gists_url": "https://api.github.com/users/hogelog/gists{/gist_id}",
443 | "starred_url": "https://api.github.com/users/hogelog/starred{/owner}{/repo}",
444 | "subscriptions_url": "https://api.github.com/users/hogelog/subscriptions",
445 | "organizations_url": "https://api.github.com/users/hogelog/orgs",
446 | "repos_url": "https://api.github.com/users/hogelog/repos",
447 | "events_url": "https://api.github.com/users/hogelog/events{/privacy}",
448 | "received_events_url": "https://api.github.com/users/hogelog/received_events",
449 | "type": "User",
450 | "site_admin": false
451 | }
452 | }
453 |
--------------------------------------------------------------------------------