├── log └── .keep ├── app ├── mailers │ └── .keep ├── assets │ ├── images │ │ ├── .keep │ │ └── github.png │ ├── stylesheets │ │ ├── static.css.scss │ │ ├── issues.css.scss │ │ ├── webhooks.css.scss │ │ ├── repositories.css.scss │ │ ├── bounties.css.scss │ │ ├── coders.css.scss │ │ ├── application.css.scss │ │ └── top4.css.scss │ └── javascripts │ │ ├── issues.js.coffee │ │ ├── webhooks.js.coffee │ │ ├── jquery.readyselector.js │ │ ├── application.js │ │ ├── bounties.js.coffee │ │ ├── repositories.js.coffee │ │ ├── coders.js.coffee │ │ └── top4.js.coffee ├── models │ ├── concerns │ │ ├── .keep │ │ ├── repo_filters.rb │ │ ├── schwarm.rb │ │ └── bounty_points.rb │ ├── git_identity.rb │ ├── issue.rb │ ├── commit.rb │ ├── coder.rb │ ├── repository.rb │ └── bounty.rb ├── controllers │ ├── concerns │ │ ├── .keep │ │ ├── slack_webhook.rb │ │ └── burndown_chart.rb │ ├── application_controller.rb │ ├── scoreboard_controller.rb │ ├── coders_controller.rb │ ├── repositories_controller.rb │ ├── coders │ │ └── omniauth_callbacks_controller.rb │ ├── bounties_controller.rb │ ├── top4_controller.rb │ └── webhooks_controller.rb ├── helpers │ ├── top4_helper.rb │ ├── issues_helper.rb │ ├── webhooks_helper.rb │ ├── repositories_helper.rb │ ├── coders_helper.rb │ ├── bounties_helper.rb │ └── application_helper.rb ├── views │ ├── repositories │ │ ├── index.html.erb │ │ ├── _repository.html.erb │ │ ├── _scoreboard.html.erb │ │ └── show.html.erb │ ├── scoreboard │ │ └── index.html.erb │ ├── top4 │ │ ├── _issue.html.erb │ │ ├── _issue_closed.html.erb │ │ └── show.html.erb │ ├── coders │ │ ├── _scoreboard.html.erb │ │ ├── _coder.html.erb │ │ └── show.html.erb │ ├── bounties │ │ ├── update_or_create.js.coffee │ │ └── index.html.erb │ ├── application │ │ └── _flash.html.erb │ ├── pages │ │ └── faq.html.erb │ └── layouts │ │ └── application.html.erb ├── services │ └── slack.rb └── actions │ └── publish_bounty.rb ├── lib ├── assets │ └── .keep ├── tasks │ └── .keep ├── capistrano │ └── tasks │ │ ├── logs.cap │ │ └── webhooks.cap └── active_record_extensions.rb ├── public ├── favicon.ico ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── .ruby-version ├── vendor └── assets │ ├── javascripts │ └── .keep │ ├── stylesheets │ ├── .keep │ └── sb-admin.css │ └── font-awesome │ ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff │ ├── less │ ├── fixed-width.less │ ├── core.less │ ├── bordered-pulled.less │ ├── rotated-flipped.less │ ├── larger.less │ ├── list.less │ ├── font-awesome.less │ ├── stacked.less │ ├── path.less │ ├── mixins.less │ └── spinning.less │ └── scss │ ├── _fixed-width.scss │ ├── _core.scss │ ├── _bordered-pulled.scss │ ├── _larger.scss │ ├── _rotated-flipped.scss │ ├── _list.scss │ ├── font-awesome.scss │ ├── _stacked.scss │ ├── _path.scss │ ├── _mixins.scss │ └── _spinning.scss ├── .rspec ├── config ├── initializers │ ├── high_voltage.rb │ ├── default_url_options.rb │ ├── cookies_serializer.rb │ ├── github.rb │ ├── mime_types.rb │ ├── session_store.rb │ ├── errbit.rb │ ├── filter_parameter_logging.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── wrap_parameters.rb │ ├── inflections.rb │ └── friendly_id.rb ├── environment.rb ├── boot.rb ├── deploy │ ├── old.rb │ └── production.rb ├── locales │ ├── en.yml │ └── devise.en.yml ├── routes.rb ├── database.yml ├── deploy.rb ├── secrets.yml ├── environments │ ├── development.rb │ ├── test.rb │ └── production.rb └── application.rb ├── bin ├── rake ├── bundle ├── rails └── setup_db.sh ├── config.ru ├── Rakefile ├── db ├── migrate │ ├── 20150829193440_make_bounty_columns_more_logical.rb │ ├── 20140930231251_create_repositories.rb │ ├── 20141203154945_create_git_identities.rb │ ├── 20140901143444_create_bounties.rb │ ├── 20141104172317_create_commits.rb │ ├── 20140630213046_create_coders.rb │ └── 20140902210829_create_issues.rb ├── seeds.rb └── schema.rb ├── spec ├── support │ └── matchers │ │ └── almost_eq.rb ├── controllers │ ├── top4_controller_spec.rb │ ├── repositories_controller_spec.rb │ └── bounties_controller_spec.rb ├── models │ ├── repository_spec.rb │ ├── commit_spec.rb │ ├── issue_spec.rb │ ├── bounty_spec.rb │ └── coder_spec.rb ├── factories │ ├── repositories.rb │ ├── commits.rb │ ├── bounties.rb │ ├── coders.rb │ └── issues.rb ├── action │ └── publish_bounty_spec.rb ├── github_jsons │ ├── ping.json │ ├── repo_create.json │ ├── push.json │ ├── issue_open.json │ └── issue_close.json ├── rails_helper.rb ├── requests │ └── webhooks_spec.rb └── spec_helper.rb ├── Capfile ├── README.md ├── .rubocop.yml ├── README.rdoc ├── .travis.yml ├── .gitignore ├── LICENSE └── Gemfile /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.2.2 2 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/helpers/top4_helper.rb: -------------------------------------------------------------------------------- 1 | module Top4Helper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/issues_helper.rb: -------------------------------------------------------------------------------- 1 | module IssuesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/webhooks_helper.rb: -------------------------------------------------------------------------------- 1 | module WebhooksHelper 2 | end 3 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | --require rails_helper 4 | -------------------------------------------------------------------------------- /app/helpers/repositories_helper.rb: -------------------------------------------------------------------------------- 1 | module RepositoriesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/assets/stylesheets/static.css.scss: -------------------------------------------------------------------------------- 1 | .faq { 2 | .inset { 3 | margin-left: 15px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /app/assets/images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DV/gamification/master/app/assets/images/github.png -------------------------------------------------------------------------------- /config/initializers/high_voltage.rb: -------------------------------------------------------------------------------- 1 | HighVoltage.configure do |config| 2 | config.routes = false 3 | end 4 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /config/initializers/default_url_options.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.default_url_options[:host] = 2 | Rails.configuration.x.host 3 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DV/gamification/master/vendor/assets/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /vendor/assets/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DV/gamification/master/vendor/assets/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /vendor/assets/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DV/gamification/master/vendor/assets/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /vendor/assets/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DV/gamification/master/vendor/assets/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../../config/application', __FILE__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :marshal 4 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/initializers/github.rb: -------------------------------------------------------------------------------- 1 | # Github accessor 2 | Rails.configuration.x.github = Github.new( 3 | oauth_token: Rails.application.secrets.github_token, 4 | auto_pagination: true 5 | ) 6 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /app/views/repositories/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%=render partial: 'scoreboard' %> 4 |
5 |
6 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | -------------------------------------------------------------------------------- /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/assets/stylesheets/issues.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the issues controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/webhooks.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the webhooks controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/helpers/coders_helper.rb: -------------------------------------------------------------------------------- 1 | module CodersHelper 2 | def avatar(coder, size: '45') 3 | img = image_tag(coder.avatar_url, alt: coder.github_name, size: size) 4 | link_to(img, coder, class: 'avatar') 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/logs.cap: -------------------------------------------------------------------------------- 1 | namespace :logs do 2 | desc "tail rails logs" 3 | task :tail do 4 | on roles(:app) do 5 | execute "tail -f #{shared_path}/log/#{fetch(:rails_env)}.log" 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store(:cookie_store, 4 | key: '_gamification_session') 5 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /app/views/scoreboard/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <%= render partial: 'coders/scoreboard' %> 5 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /config/initializers/errbit.rb: -------------------------------------------------------------------------------- 1 | Airbrake.configure do |config| 2 | config.api_key = '347b322e04831db3e24da517849b2a2b' 3 | config.host = 'zeus-errbit.ilion.me' 4 | config.port = 80 5 | config.secure = config.port == 443 6 | end 7 | -------------------------------------------------------------------------------- /app/views/top4/_issue.html.erb: -------------------------------------------------------------------------------- 1 | <% repo = issue.repository %> 2 | 3 | <%= link_to repo.name, repo %> 4 | <%= link_to issue.title, issue.github_url %> 5 | <%= issue.total_bounty_value %> 6 | 7 | -------------------------------------------------------------------------------- /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 += [:password] 5 | -------------------------------------------------------------------------------- /app/assets/javascripts/issues.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/helpers/bounties_helper.rb: -------------------------------------------------------------------------------- 1 | module BountiesHelper 2 | def find_bounty(issue, issuer) 3 | bounty = issue.unclaimed_bounties.find { |b| b.issuer == issuer } 4 | bounty = issue.bounties.build(value: 0) unless bounty 5 | bounty 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/assets/javascripts/webhooks.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | end 6 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, for example 2 | # lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Gamification::Application.load_tasks 7 | -------------------------------------------------------------------------------- /db/migrate/20150829193440_make_bounty_columns_more_logical.rb: -------------------------------------------------------------------------------- 1 | class MakeBountyColumnsMoreLogical < ActiveRecord::Migration 2 | def change 3 | rename_column :bounties, :value, :absolute_value 4 | rename_column :coders, :bounty_residual, :absolute_bounty_residual 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/controllers/scoreboard_controller.rb: -------------------------------------------------------------------------------- 1 | class ScoreboardController < ApplicationController 2 | def index 3 | @coders = Coder.only_with_stats(:additions, :deletions, :commit_count, 4 | :score) 5 | .order(score: :desc).to_a 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/support/matchers/almost_eq.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :almost_eq do |expected| 2 | epsilon = 1 3 | 4 | match do |actual| 5 | (expected - actual).abs <= epsilon 6 | end 7 | 8 | chain :with_epsilon do |custom_epsilon| 9 | epsilon = custom_epsilon 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/controllers/top4_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Top4Controller, type: :controller do 4 | describe 'GET show' do 5 | it 'returns http success' do 6 | get :show 7 | expect(response).to have_http_status(:success) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/active_record_extensions.rb: -------------------------------------------------------------------------------- 1 | # Monkey patches of ActiveRecord::Base 2 | 3 | module ActiveRecord 4 | class Base 5 | include Rails.application.routes.url_helpers 6 | 7 | def base_uri 8 | url_for(self) 9 | rescue NoMethodError 10 | url_for(self.class) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/assets/stylesheets/repositories.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the repositories controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | 5 | #top-repos { 6 | .name { 7 | font-weight: bold; 8 | padding-left: 60px; 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font-family: FontAwesome; 7 | font-style: normal; 8 | font-weight: normal; 9 | line-height: 1; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font-family: FontAwesome; 7 | font-style: normal; 8 | font-weight: normal; 9 | line-height: 1; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | -------------------------------------------------------------------------------- /app/services/slack.rb: -------------------------------------------------------------------------------- 1 | require 'httparty' 2 | require 'active_record_extensions' 3 | 4 | class Slack 5 | include HTTParty 6 | 7 | def self.send_text(text) 8 | send_hook(text: text) 9 | end 10 | 11 | def self.send_hook(options) 12 | hook_url = Rails.application.secrets.slack_hook_url 13 | post(hook_url, body: JSON.dump(options)) if hook_url 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /app/views/top4/_issue_closed.html.erb: -------------------------------------------------------------------------------- 1 | <% issue = issue_closed %> 2 | <% repo = issue.repository %> 3 | 4 | <%= link_to repo.name, repo %> 5 | <%= link_to issue.title, issue.github_url %> 6 | 7 | <% if issue.assignee %> 8 | <%= link_to issue.assignee.github_name, issue.assignee %> 9 | <%end%> 10 | 11 | 12 | -------------------------------------------------------------------------------- /db/migrate/20140930231251_create_repositories.rb: -------------------------------------------------------------------------------- 1 | class CreateRepositories < ActiveRecord::Migration 2 | def change 3 | create_table :repositories do |t| 4 | t.string :name, index: true, unique: true, null: false 5 | t.string :github_url, null: false 6 | t.string :clone_url, null: false 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20141203154945_create_git_identities.rb: -------------------------------------------------------------------------------- 1 | class CreateGitIdentities < ActiveRecord::Migration 2 | def change 3 | create_table :git_identities do |t| 4 | t.string :name, null: false 5 | t.string :email, null: false 6 | t.references :coder, index: true 7 | 8 | t.timestamps 9 | end 10 | add_index :git_identities, [:name, :email], unique: true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /config/deploy/old.rb: -------------------------------------------------------------------------------- 1 | server 'king.ugent.be', user: 'gamification', roles: %w(web app db), 2 | ssh_options: { 3 | forward_agent: true, 4 | auth_methods: ['publickey'], 5 | port: 2222 6 | } 7 | 8 | set :rails_env, 'production' 9 | set :default_env, 'RAILS_RELATIVE_URL_ROOT' => '/game' 10 | -------------------------------------------------------------------------------- /app/models/git_identity.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: git_identities 4 | # 5 | # id :integer not null, primary key 6 | # name :string(255) not null 7 | # email :string(255) not null 8 | # coder_id :integer 9 | # created_at :datetime 10 | # updated_at :datetime 11 | # 12 | 13 | class GitIdentity < ActiveRecord::Base 14 | belongs_to :coder 15 | end 16 | -------------------------------------------------------------------------------- /Capfile: -------------------------------------------------------------------------------- 1 | # Load DSL and Setup Up Stages 2 | require 'capistrano/setup' 3 | 4 | # Includes default deployment tasks 5 | require 'capistrano/deploy' 6 | 7 | require 'capistrano/rails' 8 | #require 'capistrano/rvm' 9 | require 'capistrano/rbenv' 10 | require 'capistrano/rails/collection' 11 | 12 | # Loads custom tasks from `lib/capistrano/tasks' if you have any defined. 13 | Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r } 14 | -------------------------------------------------------------------------------- /app/controllers/coders_controller.rb: -------------------------------------------------------------------------------- 1 | class CodersController < ApplicationController 2 | before_action :set_coder, only: [:show] 3 | 4 | def show 5 | @repositories = Repository.only_with_stats( 6 | :score, :commit_count, :additions, :deletions 7 | ).where(coder_id: @coder).order(score: :desc).run 8 | end 9 | 10 | private 11 | 12 | def set_coder 13 | @coder = Coder.friendly.find params[:id] 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /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 | # Precompile additional assets. 7 | # application.js, application.css, and all non-JS/CSS in app/assets folder are 8 | # already added. 9 | # Rails.application.config.assets.precompile += %w( search.js ) 10 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .pull-right { float: right; } 11 | .pull-left { float: left; } 12 | 13 | .@{fa-css-prefix} { 14 | &.pull-left { margin-right: .3em; } 15 | &.pull-right { margin-left: .3em; } 16 | } 17 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .pull-right { float: right; } 11 | .pull-left { float: left; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.pull-left { margin-right: .3em; } 15 | &.pull-right { margin-left: .3em; } 16 | } 17 | -------------------------------------------------------------------------------- /app/views/repositories/_repository.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%= repository_counter + 1 %>. 3 | <%= link_to repository.name, repository %> 4 | <%= format_score repository.score%> 5 | <%= format_score repository.commit_count %> 6 | <%= format_score repository.additions %> 7 | <%= format_score repository.deletions %> 8 | 9 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /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 4 | # wish to see in your backtraces. 5 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 6 | 7 | # You can also remove all the silencers if you're trying to debug a problem 8 | # that might stem from framework code. 9 | # Rails.backtrace_cleaner.remove_silencers! 10 | -------------------------------------------------------------------------------- /spec/models/repository_spec.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: repositories 4 | # 5 | # id :integer not null, primary key 6 | # name :string(255) not null 7 | # github_url :string(255) not null 8 | # clone_url :string(255) not null 9 | # created_at :datetime 10 | # updated_at :datetime 11 | # 12 | 13 | describe Repository do 14 | it 'has a valid factory' do 15 | expect(create :repository).to be_valid 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: -@fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Gamification [![Build Status](https://travis-ci.org/ZeusWPI/gamification.png?branch=master)](https://travis-ci.org/ZeusWPI/gamification) [![Coverage Status](https://coveralls.io/repos/ZeusWPI/gamification/badge.png?branch=master)](https://coveralls.io/r/ZeusWPI/gamification) [![Code Climate](https://codeclimate.com/github/ZeusWPI/gamification/badges/gpa.svg)](https://codeclimate.com/github/ZeusWPI/gamification) 2 | ======= 3 | 4 | Gamification of Zeus member engagement (with GitHub integration) 5 | -------------------------------------------------------------------------------- /config/deploy/production.rb: -------------------------------------------------------------------------------- 1 | server 'zeus.ugent.be', user: 'gamification', roles: %w(web app db), 2 | ssh_options: { 3 | forward_agent: true, 4 | auth_methods: ['publickey'], 5 | port: 2222 6 | } 7 | 8 | set :rails_env, 'production' 9 | set :rbenv_type, :system 10 | set :rbenv_ruby, File.read('.ruby-version').strip 11 | set :default_env, 'RAILS_RELATIVE_URL_ROOT' => '/game' 12 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/models/concerns/repo_filters.rb: -------------------------------------------------------------------------------- 1 | module RepoFilters 2 | def self.track?(repo) 3 | filters.all? { |k, v| FILTER_LAMBDAS[k].call(v, repo) } 4 | end 5 | 6 | private 7 | 8 | def self.filters 9 | Rails.application.config.repository_filters 10 | end 11 | 12 | FILTER_LAMBDAS = { 13 | only: ->(list, repo) { list.include? repo['name'] }, 14 | except: ->(list, repo) { !list.include? repo['name'] }, 15 | private: ->(bool, repo) { bool == repo['private'] } 16 | } 17 | end 18 | -------------------------------------------------------------------------------- /app/assets/javascripts/jquery.readyselector.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | var ready = $.fn.ready; 3 | $.fn.ready = function (fn) { 4 | if (this.context === undefined) { 5 | // The $().ready(fn) case. 6 | ready(fn); 7 | } else if (this.selector) { 8 | ready($.proxy(function(){ 9 | $(this.selector, this.context).each(fn); 10 | }, this)); 11 | } else { 12 | ready($.proxy(function(){ 13 | $(this).each(fn); 14 | }, this)); 15 | } 16 | } 17 | })(jQuery); 18 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/webhooks.cap: -------------------------------------------------------------------------------- 1 | namespace :webhooks do 2 | 3 | desc 'Setup github webhooks' 4 | task :setup do 5 | on roles(:app) do 6 | within current_path do 7 | execute :rake, 'webhooks:create RAILS_ENV=production' 8 | end 9 | end 10 | end 11 | 12 | desc 'Clear github webhooks' 13 | task :delete do 14 | on roles(:app) do 15 | within current_path do 16 | execute :rake, 'webhooks:delete RAILS_ENV=production' 17 | end 18 | end 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "spinning"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.0.3 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "spinning"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | -------------------------------------------------------------------------------- /spec/controllers/repositories_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe RepositoriesController, type: :controller do 4 | describe 'GET index' do 5 | it 'returns http success' do 6 | get :index 7 | expect(response).to have_http_status(:success) 8 | end 9 | end 10 | 11 | describe 'GET show' do 12 | it 'returns http success' do 13 | @repo = create :repository 14 | get :show, id: @repo 15 | expect(response).to have_http_status(:success) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | RunRailsCops: true 3 | Include: 4 | - Rakefile 5 | - config.ru 6 | Exclude: 7 | - db/schema.rb 8 | - repos/**/* 9 | 10 | 11 | # We right self-documenting code, right? Disabling for now. 12 | Style/Documentation: 13 | Enabled: false 14 | 15 | # Tests don't have to be awfully readable 16 | Metrics/LineLength: 17 | Exclude: 18 | - spec/**/* 19 | 20 | Metrics/AbcSize: 21 | Enabled: false 22 | Metrics/MethodLength: 23 | Max: 20 24 | Metrics/CyclomaticComplexity: 25 | Enabled: false 26 | -------------------------------------------------------------------------------- /app/views/repositories/_scoreboard.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <%= render partial: 'repositories/repository', collection: @repositories%> 14 | 15 |
RepoScoreCommitsAdditionsDeletions
16 | -------------------------------------------------------------------------------- /db/migrate/20140901143444_create_bounties.rb: -------------------------------------------------------------------------------- 1 | class CreateBounties < ActiveRecord::Migration 2 | def change 3 | create_table :bounties do |t| 4 | t.integer :value, null: false 5 | t.references :issue, null: false, index: true 6 | t.references :issuer, null: false, index: true 7 | t.references :claimant, index: true 8 | t.integer :claimed_value 9 | t.timestamp :claimed_at, index: true 10 | 11 | t.timestamps 12 | end 13 | add_index :bounties, [:issue_id, :issuer_id] 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20141104172317_create_commits.rb: -------------------------------------------------------------------------------- 1 | class CreateCommits < ActiveRecord::Migration 2 | def change 3 | create_table :commits do |t| 4 | t.references :coder, index: true 5 | t.references :repository, index: true 6 | t.string :sha, null: false 7 | t.integer :additions, null: false, default: 0 8 | t.integer :deletions, null: false, default: 0 9 | t.timestamp :date, null: false, index: true 10 | 11 | t.timestamps 12 | end 13 | add_index :commits, [:repository_id, :sha], unique: true 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /spec/factories/repositories.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: repositories 4 | # 5 | # id :integer not null, primary key 6 | # name :string(255) not null 7 | # github_url :string(255) not null 8 | # clone_url :string(255) not null 9 | # created_at :datetime 10 | # updated_at :datetime 11 | # 12 | 13 | require 'faker' 14 | 15 | FactoryGirl.define do 16 | factory :repository do 17 | name { Faker::Lorem.word } 18 | github_url { "example.com/#{name}" } 19 | clone_url { "https://example.com/#{name}.git" } 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | == README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | 26 | 27 | Please feel free to use a different markup language if you do not plan to run 28 | rake doc:app. 29 | -------------------------------------------------------------------------------- /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 7 | # to an empty array. 8 | ActiveSupport.on_load(:action_controller) do 9 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 10 | end 11 | 12 | # To enable root element in JSON for ActiveRecord objects. 13 | # ActiveSupport.on_load(:active_record) do 14 | # self.include_root_in_json = true 15 | # end 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | sudo: false 3 | rvm: 4 | - 2.2.2 5 | before_script: 6 | - RAILS_ENV=test bundle exec rake db:create 7 | - RAILS_ENV=test bundle exec rake db:schema:load 8 | script: bundle exec rspec 9 | notifications: 10 | slack: zeuswpi:xpKSw4tg8JIvQb7Z3svBq0tD 11 | email: 12 | recipients: 13 | - gamification@zeus.ugent.be 14 | on_success: never 15 | on_failure: change 16 | env: 17 | matrix: 18 | secure: r6SIoQiox7bomDrNNFRpVKz7VVaeg8nkUsT10u+2EAHT0NVCSz7EQeLB2Xw6xqSnkZxxm2Bp5rOwEaQQkU4Aiv7lpjBmhlpWxv2McYOG73ee0J5bdVi+f+MyuUY9c4F6J44y1CO68betCPOytivR/zdaoBP2Y/1VRMgAyHiovLI= 19 | -------------------------------------------------------------------------------- /spec/models/commit_spec.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: commits 4 | # 5 | # id :integer not null, primary key 6 | # coder_id :integer 7 | # repository_id :integer 8 | # sha :string(255) not null 9 | # additions :integer default(0), not null 10 | # deletions :integer default(0), not null 11 | # date :datetime not null 12 | # created_at :datetime 13 | # updated_at :datetime 14 | # 15 | 16 | describe Commit do 17 | it 'has a valid factory' do 18 | expect(create :commit).to be_valid 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/views/coders/_scoreboard.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <%= render partial: 'coders/coder', collection: @coders %> 16 | 17 |
ContributorScoreCommitsAdditionsDeletions
18 |
19 | -------------------------------------------------------------------------------- /app/controllers/repositories_controller.rb: -------------------------------------------------------------------------------- 1 | class RepositoriesController < ApplicationController 2 | def index 3 | @repositories = Repository 4 | .with_stats(:score, :commit_count, :additions, :deletions) 5 | .order(score: :desc).run 6 | end 7 | 8 | def show 9 | @repository = Repository.friendly.find params[:id] 10 | @coders = Coder.only_with_stats(:score, :commit_count, :additions, 11 | :deletions) 12 | .where(repository: @repository).order(score: :desc).run 13 | @chart = BurndownChart.new(@repository.issues).timeline 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /bin/setup_db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Run this as root to setup the MySQL database 4 | 5 | # First off, run a MySQL daemon, e.g. `sudo systemctl start mysqld`. 6 | # Second, create databases and users 7 | mysql -u root < 10 | plaatste <#{bounty.base_uri}|#{bounty.value} bountypunten> op 11 | <#{bounty.issue.repository.base_uri}|#{bounty.issue.repository.name}> 12 | #<#{bounty.issue.github_url}|#{bounty.issue.number}: 13 | #{bounty.issue.title}> 14 | EOF 15 | 16 | hook_url = Rails.application.secrets.slack_hook_url 17 | post(hook_url, body: JSON.dump(text: message)) if hook_url 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/views/coders/_coder.html.erb: -------------------------------------------------------------------------------- 1 | class="info"<% end %>> 2 | <%= coder_counter + 1 %>. 3 | 4 | <%= avatar coder %> 5 | 6 | 7 | <%= link_to coder.github_name, coder, class: "username" %> 8 |
9 | <% unless coder.full_name.blank? %> 10 | <%= coder.full_name %> 11 | <% end %> 12 | 13 | <%= format_score coder.score%> 14 | <%= format_score coder.commit_count %> 15 | <%= format_score coder.additions %> 16 | <%= format_score coder.deletions %> 17 | 18 | -------------------------------------------------------------------------------- /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 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /app/assets/stylesheets/bounties.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the bounties controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | 5 | #bounties-table { 6 | td, th { 7 | vertical-align: middle; 8 | overflow: hidden; 9 | 10 | &.total-bounty { 11 | text-align: center; 12 | } 13 | 14 | &.my-bounty { 15 | text-align: center; 16 | min-width: 9em; 17 | padding-left: 0; 18 | input { 19 | max-width: 4em; 20 | } 21 | input[type=submit] { 22 | min-width: 4em; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 9 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 10 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 11 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | root 'top4#show' 3 | get 'top4/show' 4 | 5 | scope path: 'scoreboard', as: 'scoreboard' do 6 | get '' => 'scoreboard#index' 7 | end 8 | 9 | post 'payload', to: 'webhooks#receive' 10 | 11 | resources :coders, only: [:show] 12 | resources :repositories, only: [:index, :show] 13 | resources :bounties, only: [:index] do 14 | post 'update_or_create', on: :collection 15 | end 16 | 17 | devise_for(:coders, controllers: { 18 | omniauth_callbacks: 'coders/omniauth_callbacks' 19 | }) 20 | devise_scope :coder do 21 | get 'sign_out', to: 'devise/sessions#destroy', as: :destroy_session 22 | end 23 | 24 | get '/faq' => 'high_voltage/pages#show', :id => 'faq' 25 | end 26 | -------------------------------------------------------------------------------- /spec/factories/commits.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: commits 4 | # 5 | # id :integer not null, primary key 6 | # coder_id :integer 7 | # repository_id :integer 8 | # sha :string(255) not null 9 | # additions :integer default(0), not null 10 | # deletions :integer default(0), not null 11 | # date :datetime not null 12 | # created_at :datetime 13 | # updated_at :datetime 14 | # 15 | 16 | require 'faker' 17 | 18 | FactoryGirl.define do 19 | factory :commit do 20 | coder 21 | repository 22 | sha { Faker::Lorem.characters 30 } 23 | additions { rand 100 } 24 | deletions { rand 100 } 25 | date { Faker::Date.backward 30 } 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/models/concerns/schwarm.rb: -------------------------------------------------------------------------------- 1 | module Schwarm 2 | CommitFisch = Datenfisch.provider Commit do 3 | stat :additions, col(:additions).sum 4 | stat :deletions, col(:deletions).sum 5 | stat :count, count 6 | stat :addition_score, ( 7 | ln(col(:additions) + 1) * 8 | -> { Rails.application.config.addition_score_factor } 9 | ).round.sum 10 | end 11 | 12 | BountyFisch = Datenfisch.provider Bounty do 13 | stat :claimed_value, col(:claimed_value).sum 14 | stat :absolute_bounty_value, col(:absolute_value).sum 15 | 16 | # rubocop:disable Style/Attr 17 | attr :repository_id, :repository_id, through: :issue 18 | attr :coder_id, :claimant_id 19 | attr :date, :claimed_at 20 | # rubocop:enable Style/Attr 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: mysql2 8 | username: gamification 9 | database: gamification 10 | password: hallo 11 | host: 127.0.0.1 12 | port: 3306 13 | 14 | # Warning: The database defined as "test" will be erased and 15 | # re-generated from your development database when you run "rake". 16 | # Do not set this db to the same as development or production. 17 | test: 18 | adapter: mysql2 19 | username: travis 20 | database: gamification_test 21 | 22 | production: 23 | adapter: mysql2 24 | database: gamification 25 | username: gamification 26 | password: #dev# 27 | host: 127.0.0.1 28 | port: 3306 29 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 9 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 10 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 11 | //src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | -------------------------------------------------------------------------------- /db/migrate/20140902210829_create_issues.rb: -------------------------------------------------------------------------------- 1 | class CreateIssues < ActiveRecord::Migration 2 | def change 3 | create_table :issues do |t| 4 | t.string :github_url, null: false 5 | t.integer :number, null: false 6 | t.string :title, null: false, default: 'Untitled' 7 | t.references :issuer, null: false 8 | t.references :repository, null: false, index: true 9 | t.text :labels, null: false 10 | t.text :body 11 | t.integer :assignee_id 12 | t.string :milestone 13 | 14 | t.timestamp :opened_at, null: false 15 | t.timestamp :closed_at 16 | 17 | t.timestamps 18 | end 19 | add_index :issues, [:repository_id, :number], unique: true 20 | add_index :issues, :github_url, unique: true 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon-rotate(@degrees, @rotation) { 5 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); 6 | -webkit-transform: rotate(@degrees); 7 | -moz-transform: rotate(@degrees); 8 | -ms-transform: rotate(@degrees); 9 | -o-transform: rotate(@degrees); 10 | transform: rotate(@degrees); 11 | } 12 | 13 | .fa-icon-flip(@horiz, @vert, @rotation) { 14 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); 15 | -webkit-transform: scale(@horiz, @vert); 16 | -moz-transform: scale(@horiz, @vert); 17 | -ms-transform: scale(@horiz, @vert); 18 | -o-transform: scale(@horiz, @vert); 19 | transform: scale(@horiz, @vert); 20 | } 21 | -------------------------------------------------------------------------------- /spec/factories/bounties.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: bounties 4 | # 5 | # id :integer not null, primary key 6 | # absolute_value :integer not null 7 | # issue_id :integer not null 8 | # issuer_id :integer not null 9 | # claimant_id :integer 10 | # claimed_value :integer 11 | # claimed_at :datetime 12 | # created_at :datetime 13 | # updated_at :datetime 14 | # 15 | 16 | FactoryGirl.define do 17 | factory :bounty do 18 | absolute_value { rand 100 } 19 | issue 20 | association :issuer, factory: :coder 21 | 22 | factory :claimed_bounty do 23 | association :claimant, factory: :coder 24 | claimed_at { Faker::Date.backward 30 } 25 | claimed_value { value } 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon-rotate($degrees, $rotation) { 5 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation); 6 | -webkit-transform: rotate($degrees); 7 | -moz-transform: rotate($degrees); 8 | -ms-transform: rotate($degrees); 9 | -o-transform: rotate($degrees); 10 | transform: rotate($degrees); 11 | } 12 | 13 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 14 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=$rotation); 15 | -webkit-transform: scale($horiz, $vert); 16 | -moz-transform: scale($horiz, $vert); 17 | -ms-transform: scale($horiz, $vert); 18 | -o-transform: scale($horiz, $vert); 19 | transform: scale($horiz, $vert); 20 | } 21 | -------------------------------------------------------------------------------- /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 | 18 | ActiveSupport::Inflector.inflections(:en) do |inflect| 19 | inflect.irregular 'bountypunt', 'bountypunten' 20 | end 21 | -------------------------------------------------------------------------------- /spec/action/publish_bounty_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe PublishBounty do 4 | 5 | it "sends a message with Slack" do 6 | bounty = build :bounty 7 | expect(Slack).to receive(:send_text) 8 | 9 | PublishBounty.new(bounty).call 10 | end 11 | 12 | it "generates correct message for bounties with value 1" do 13 | bounty = build :bounty, value: 1 14 | 15 | expect(Slack).to receive(:send_text) do |text| 16 | expect(text).to include("1 bountypunt") 17 | end 18 | 19 | PublishBounty.new(bounty).call 20 | end 21 | 22 | it "generates correct message for bounties with plural value" do 23 | bounty = build :bounty, value: 2 24 | 25 | expect(Slack).to receive(:send_text) do |text| 26 | expect(text).to include("2 bountypunten") 27 | end 28 | 29 | PublishBounty.new(bounty).call 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def alert_class_for(flash_type) 3 | { 4 | success: 'alert-success', 5 | error: 'alert-danger', 6 | alert: 'alert-warning', 7 | warning: 'alert-warning', 8 | notice: 'alert-info' 9 | }[flash_type.to_sym] || flash_type.to_s 10 | end 11 | 12 | def format_score(score) 13 | number_with_delimiter(score, delimiter: ' '.html_safe) 14 | end 15 | 16 | def format_short_score(score) 17 | number_to_human(score, delimiter: ' '.html_safe, separator: '.', 18 | format: '%n %u'.html_safe, 19 | units: { thousand: 'K', million: 'M', billion: 'G' }) 20 | end 21 | 22 | def navbar_item(html, target) 23 | class_str = current_page?(target) ? 'active' : '' 24 | content_tag(:li, link_to(html, target), class: class_str) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/views/bounties/update_or_create.js.coffee: -------------------------------------------------------------------------------- 1 | $('#flash-messages').append '<%= escape_javascript render 'flash', locals: { flash: flash } %>' 2 | 3 | $tr = $('tr#issue-<%= @issue.id %>') 4 | $totalBountyCell = $tr.find('.total-bounty') 5 | 6 | # Remove previous styling 7 | $tr.removeClass('has-error has-success') 8 | $tr.find('.btn').removeClass('btn-danger btn-success btn-default') 9 | 10 | <% if flash[:error] %> 11 | 12 | # Scroll to error 13 | $(document).scrollTop(0) 14 | # Indicate the violating cell 15 | $tr.addClass('has-error') 16 | $tr.find('.btn').addClass('btn-danger') 17 | 18 | <% else %> 19 | 20 | # Update the total bounty value 21 | $totalBountyCell.text <%= @issue.total_bounty_value %> 22 | # Update the total bounty points that can be spend 23 | $('#remaining-points').text <%= current_coder.bounty_residual %> 24 | $tr.addClass('has-success') 25 | $tr.find('.btn').addClass('btn-success') 26 | 27 | <% end %> 28 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/less/spinning.less: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: spin 2s infinite linear; 6 | -moz-animation: spin 2s infinite linear; 7 | -o-animation: spin 2s infinite linear; 8 | animation: spin 2s infinite linear; 9 | } 10 | 11 | @-moz-keyframes spin { 12 | 0% { -moz-transform: rotate(0deg); } 13 | 100% { -moz-transform: rotate(359deg); } 14 | } 15 | @-webkit-keyframes spin { 16 | 0% { -webkit-transform: rotate(0deg); } 17 | 100% { -webkit-transform: rotate(359deg); } 18 | } 19 | @-o-keyframes spin { 20 | 0% { -o-transform: rotate(0deg); } 21 | 100% { -o-transform: rotate(359deg); } 22 | } 23 | @-ms-keyframes spin { 24 | 0% { -ms-transform: rotate(0deg); } 25 | 100% { -ms-transform: rotate(359deg); } 26 | } 27 | @keyframes spin { 28 | 0% { transform: rotate(0deg); } 29 | 100% { transform: rotate(359deg); } 30 | } 31 | -------------------------------------------------------------------------------- /vendor/assets/font-awesome/scss/_spinning.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: spin 2s infinite linear; 6 | -moz-animation: spin 2s infinite linear; 7 | -o-animation: spin 2s infinite linear; 8 | animation: spin 2s infinite linear; 9 | } 10 | 11 | @-moz-keyframes spin { 12 | 0% { -moz-transform: rotate(0deg); } 13 | 100% { -moz-transform: rotate(359deg); } 14 | } 15 | @-webkit-keyframes spin { 16 | 0% { -webkit-transform: rotate(0deg); } 17 | 100% { -webkit-transform: rotate(359deg); } 18 | } 19 | @-o-keyframes spin { 20 | 0% { -o-transform: rotate(0deg); } 21 | 100% { -o-transform: rotate(359deg); } 22 | } 23 | @-ms-keyframes spin { 24 | 0% { -ms-transform: rotate(0deg); } 25 | 100% { -ms-transform: rotate(359deg); } 26 | } 27 | @keyframes spin { 28 | 0% { transform: rotate(0deg); } 29 | 100% { transform: rotate(359deg); } 30 | } 31 | -------------------------------------------------------------------------------- /spec/factories/coders.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: coders 4 | # 5 | # id :integer not null, primary key 6 | # github_name :string(255) not null 7 | # full_name :string(255) default(""), not null 8 | # avatar_url :string(255) not null 9 | # github_url :string(255) not null 10 | # reward_residual :integer default(0), not null 11 | # absolute_bounty_residual :integer default(0), not null 12 | # other_score :integer default(0), not null 13 | # created_at :datetime 14 | # updated_at :datetime 15 | # 16 | 17 | require 'faker' 18 | 19 | FactoryGirl.define do 20 | factory :coder do 21 | github_name { Faker::Internet.user_name } 22 | full_name { Faker::Name.name } 23 | avatar_url { Faker::Avatar.image } 24 | github_url { "example.com/#{github_name}" } 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /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 vendor/assets/javascripts of plugins, if any, 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. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery.turbolinks 15 | //= require jquery_ujs 16 | //= require dataTables/jquery.dataTables 17 | //= require dataTables/bootstrap/3/jquery.dataTables.bootstrap 18 | //= require turbolinks 19 | //= require jquery.readyselector 20 | 21 | //= require bootstrap-sprockets 22 | //= require d3 23 | 24 | //= require_self 25 | //= require_tree . 26 | -------------------------------------------------------------------------------- /app/controllers/bounties_controller.rb: -------------------------------------------------------------------------------- 1 | class BountiesController < ApplicationController 2 | before_action :authenticate_coder!, only: [:update_or_create] 3 | 4 | respond_to :html, :coffee 5 | 6 | def index 7 | @issues = Issue.statted.where(closed_at: nil) 8 | .with_stats(:absolute_bounty_value) 9 | .includes(:repository, :unclaimed_bounties).run 10 | end 11 | 12 | def update_or_create 13 | new_value = bounty_params[:value] 14 | # Value must be a non-negative integer 15 | unless new_value =~ /^\d+$/ 16 | flash.now[:error] = 'This value is not an integer.' 17 | return 18 | end 19 | 20 | @issue = Issue.find(bounty_params[:issue_id]) 21 | 22 | begin 23 | Bounty.update_or_create(@issue, current_coder, new_value.to_i) 24 | rescue Bounty::Error => error 25 | flash.now[:error] = error.message 26 | end 27 | end 28 | 29 | private 30 | 31 | def bounty_params 32 | params.require(:bounty).permit(:issue_id, :value) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **.orig 2 | *.gem 3 | *.rbc 4 | /.config 5 | /InstalledFiles 6 | /config/secrets.yml 7 | /coverage/ 8 | /db/*.sqlite3 9 | /log 10 | /pkg/ 11 | /public/system 12 | /spec/reports/ 13 | /test/tmp/ 14 | /test/version_tmp/ 15 | /tmp/ 16 | capybara-*.html 17 | pickle-email-*.html 18 | rerun.txt 19 | 20 | ## Specific to RubyMotion: 21 | .dat* 22 | .repl_history 23 | build/ 24 | 25 | ## Documentation cache and generated files: 26 | /.yardoc/ 27 | /_yardoc/ 28 | /doc/ 29 | /rdoc/ 30 | 31 | ## Environment normalisation: 32 | /.bundle/ 33 | /lib/bundler/man/ 34 | /vendor/bundle 35 | 36 | # for a library or gem, you might want to ignore these files since the code is 37 | # intended to run in multiple environments; otherwise, check them in: 38 | # Gemfile.lock 39 | # .ruby-version 40 | # .ruby-gemset 41 | 42 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 43 | .rvmrc 44 | 45 | # Ignore repository folder 46 | repos/* 47 | 48 | # Capistrano 49 | .capistrano 50 | 51 | # RSpec failure-file 52 | spec/.failures.txt 53 | -------------------------------------------------------------------------------- /spec/factories/issues.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: issues 4 | # 5 | # id :integer not null, primary key 6 | # github_url :string(255) not null 7 | # number :integer not null 8 | # title :string(255) default("Untitled"), not null 9 | # issuer_id :integer not null 10 | # repository_id :integer not null 11 | # labels :text not null 12 | # body :text 13 | # assignee_id :integer 14 | # milestone :string(255) 15 | # opened_at :datetime not null 16 | # closed_at :datetime 17 | # created_at :datetime 18 | # updated_at :datetime 19 | # 20 | 21 | require 'faker' 22 | 23 | FactoryGirl.define do 24 | factory :issue do 25 | sequence :number 26 | title { Faker::Lorem.sentence } 27 | github_url { "example.org/issues/#{number}" } 28 | opened_at { Faker::Date.backward 30 } 29 | labels '' 30 | 31 | association :issuer, factory: :coder 32 | repository 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /app/actions/publish_bounty.rb: -------------------------------------------------------------------------------- 1 | class PublishBounty 2 | attr :bounty 3 | 4 | def initialize(bounty) 5 | @bounty = bounty 6 | end 7 | 8 | def call 9 | Slack.send_text(message) 10 | end 11 | 12 | private 13 | 14 | def message 15 | "#{author} plaatste #{score} op #{repository} ##{github_issue}: #{title}" 16 | end 17 | 18 | def author 19 | link_to(bounty.issuer.github_name, bounty.issuer) 20 | end 21 | 22 | def score 23 | [bounty.value, "bountypunt".pluralize(bounty.value)].join(" ") 24 | end 25 | 26 | def repository 27 | link_to(bounty.issue.repository.name, bounty.issue.repository) 28 | end 29 | 30 | def github_issue 31 | link_to(bounty.issue.number, bounty.issue.github_url) 32 | end 33 | 34 | def title 35 | bounty.issue.title 36 | end 37 | 38 | def link_to(text, object_or_uri) 39 | uri = ensure_uri(object_or_uri) 40 | 41 | "<#{uri}|#{text}>" 42 | end 43 | 44 | def ensure_uri(object_or_uri) 45 | if object_or_uri.respond_to?(:base_uri) 46 | object_or_uri.base_uri 47 | else 48 | object_or_uri 49 | end 50 | end 51 | 52 | end 53 | -------------------------------------------------------------------------------- /app/assets/stylesheets/coders.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Coder controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | 5 | #profile_pic { 6 | border-radius: 50%; 7 | box-shadow: 0 0 0 3px #fff, 0 0 0 4px #999, 0 2px 5px 4px rgba(0,0,0,.2); 8 | } 9 | .number { 10 | font-weight:lighter; 11 | font-size: 200%; 12 | } 13 | .metric { 14 | font-size: 18pt; 15 | } 16 | .score-table { 17 | text-align: center; 18 | } 19 | 20 | .scoreboard { 21 | .rank { 22 | font-size: 20pt; 23 | text-align: right; 24 | width: 45px; 25 | height: 45px; 26 | vertical-align: middle; 27 | } 28 | .avatar { 29 | width: 45px; 30 | } 31 | .name { 32 | vertical-align: middle; 33 | } 34 | .username { 35 | font-weight: bold; 36 | } 37 | .score, .commits, .additions, .deletions { 38 | vertical-align: middle; 39 | text-align: right; 40 | } 41 | } 42 | 43 | .avatar { 44 | img { 45 | border-radius: 50%; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Zeus WPI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /config/deploy.rb: -------------------------------------------------------------------------------- 1 | set :application, 'gamification' 2 | set :repo_url, 'git@github.com:ZeusWPI/gamification.git' 3 | 4 | # Default branch is :master 5 | # ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp } 6 | 7 | # Default deploy_to directory is /var/www/my_app 8 | set :branch, 'master' 9 | set :deploy_to, '/home/gamification/production' 10 | 11 | # Default value for :linked_files is [] 12 | set :linked_files, %w(config/database.yml config/secrets.yml) 13 | 14 | # Default value for linked_dirs is [] 15 | set :linked_dirs, %w(log repos tmp/pids tmp/cache tmp/sockets vendor/bundle 16 | public/system) 17 | 18 | # Default value for default_env is {} 19 | # set :default_env, { path: "/opt/ruby/bin:$PATH" } 20 | # 21 | set :log_level, :debug 22 | 23 | # Default value for keep_releases is 5 24 | # set :keep_releases, 5 25 | 26 | namespace :deploy do 27 | desc 'Restart application' 28 | task :restart do 29 | on roles(:app) do 30 | with rails_env: fetch(:rails_env) do 31 | execute "touch #{current_path}/tmp/restart.txt" 32 | end 33 | end 34 | end 35 | 36 | after :publishing, :restart 37 | end 38 | -------------------------------------------------------------------------------- /app/controllers/top4_controller.rb: -------------------------------------------------------------------------------- 1 | class Top4Controller < ApplicationController 2 | include Schwarm 3 | def show 4 | @coders = Coder.with_stats(:score) 5 | .where(date: 1.week.ago..Time.current).order(score: :desc).take(4) 6 | 7 | @top_repos = Repository.with_stats(:score) 8 | .where(date: 1.week.ago..Time.current) 9 | .order(score: :desc).take(4) 10 | 11 | @repo_contributors = @top_repos.map do |repo| 12 | Coder.only_with_stats(:score) 13 | .where(repository: repo, date: 1.weeks.ago..Time.current) 14 | .order(score: :desc).run 15 | end 16 | 17 | @new_issues = Issue.with_stats(:absolute_bounty_value) 18 | .includes(:repository) 19 | .order(opened_at: :desc).take(4) 20 | 21 | @closed_issues = Issue.where.not(closed_at: nil) 22 | .includes(:repository, :assignee) 23 | .order(closed_at: :desc).take(4) 24 | 25 | @top_issues = Issue.with_stats(:absolute_bounty_value) 26 | .includes(:repository) 27 | .order(absolute_bounty_value: :desc).take(4) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/models/concerns/bounty_points.rb: -------------------------------------------------------------------------------- 1 | module BountyPoints 2 | def self.bounty_points_from_abs(value) 3 | (value * bounty_factor).round 4 | end 5 | 6 | def self.bounty_points_to_abs(value) 7 | (value / bounty_factor).round 8 | end 9 | 10 | def self.bounty_factor 11 | if total_bounty_points < Rails.application.config.total_bounty_value 12 | 1 13 | else 14 | Rails.application.config.total_bounty_value.to_f / total_bounty_points 15 | end 16 | end 17 | 18 | def self.total_bounty_points 19 | coder_bounty_points + assigned_bounty_points 20 | end 21 | 22 | def self.coder_bounty_points 23 | Rails.cache.fetch('stats_coder_bounty_points') do 24 | Coder.sum(:absolute_bounty_residual) 25 | end 26 | end 27 | 28 | def self.assigned_bounty_points 29 | Rails.cache.fetch('stats_assigned_bounty_points') do 30 | Bounty.sum(:absolute_value) 31 | end 32 | end 33 | 34 | def self.expire_assigned_bounty_points 35 | Rails.cache.delete('stats_assigned_bounty_points') 36 | end 37 | 38 | def self.expire_coder_bounty_points 39 | Rails.cache.delete('stats_coder_bounty_points') 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/views/application/_flash.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if flash[:error] %> 3 |
4 | 5 | Error! <%= flash[:error] %> 6 |
7 | <% end %> 8 | <% if flash[:success] %> 9 |
10 | 11 | Success! <%= flash[:success] %> 12 |
13 | <% end %> 14 | <% if flash[:notice] %> 15 |
16 | 17 | Notice! <%= flash[:notice] %> 18 |
19 | <% end %> 20 | <% if flash[:warning] %> 21 |
22 | 23 | Warning! <%= flash[:warning] %> 24 |
25 | <% end %> 26 |
27 | -------------------------------------------------------------------------------- /app/assets/javascripts/bounties.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | 5 | $('body.bounties.index').ready -> 6 | 7 | # Added a parser for the my-bounty input fields 8 | $.fn.dataTable.ext.order['dom-input-numeric'] = (_settings, col) -> 9 | return this.api().column(col, {order: 'index'}).nodes().map (td, _i) -> 10 | return $('input#bounty_value', td).val() * 1 11 | 12 | $('#bounties-table').DataTable 13 | order: [[2, 'desc'], [0, 'asc'], [1, 'asc']] 14 | columnDefs: [ 15 | targets: ['total-bounty', 'my-bounty'] 16 | orderSequence: ['desc', 'asc'] 17 | render: (data, type, _row, _meta) -> 18 | if type is 'display' 19 | return data 20 | else 21 | return data.replace(/[^\d]/g, '') 22 | , 23 | targets: ['my-bounty'] 24 | orderDataType: 'dom-input-numeric' 25 | type: 'numeric' 26 | ] 27 | autoWidth: false 28 | -------------------------------------------------------------------------------- /app/views/repositories/show.html.erb: -------------------------------------------------------------------------------- 1 | <% if @repository.blank? %> 2 |
3 | This repository does not exist. 4 |
5 | <% else %> 6 |
7 |
8 | 15 | 16 | 20 | 21 |
22 |
23 |
24 | <%= render partial: 'coders/scoreboard' %> 25 |
26 | 27 |
28 |
29 | <%= render_chart(@chart, 'chart') %> 30 |
31 |
32 |
33 |
34 | <% end %> 35 | -------------------------------------------------------------------------------- /app/assets/javascripts/repositories.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | 5 | $('body.repositories.index').ready -> 6 | table = $('#top-repos').DataTable 7 | order: [[2, 'desc']] 8 | columnDefs: [ 9 | targets: 'rank' 10 | orderable: false 11 | searchable: false 12 | , 13 | targets: ['score', 'commits', 'additions', 'deletions'] 14 | orderSequence: ['desc', 'asc'] 15 | render: (data, type, _row, _meta) -> 16 | if type is 'display' 17 | return data 18 | else 19 | return data.replace(/[^\d]/g, '') 20 | ] 21 | bFilter: false 22 | paging: false 23 | autoWidth: false 24 | 25 | # Recalculate rank column when table changes, so it stays fixed 26 | table.on 'order.dt search.dt', -> 27 | table.column '.rank', 28 | search: 'applied' 29 | order: 'applied' 30 | .nodes().each (cell, i) -> 31 | cell.innerHTML = (i + 1) + '.' 32 | -------------------------------------------------------------------------------- /app/assets/javascripts/coders.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | 5 | $('body.scoreboard.index').ready -> 6 | table = $('#scoreboard').DataTable 7 | order: [[3, 'desc']] 8 | columnDefs: [ 9 | targets: ['rank', 'avatar'] 10 | orderable: false 11 | searchable: false 12 | , 13 | targets: ['score', 'commits', 'additions', 'deletions'] 14 | orderSequence: ['desc', 'asc'] 15 | render: (data, type, _row, _meta) -> 16 | if type is 'display' 17 | return data 18 | else 19 | return data.replace(/[^\d]/g, '') 20 | ] 21 | bFilter: false 22 | paging: false 23 | autoWidth: false 24 | 25 | # Recalculate rank column when table changes, so it stays fixed 26 | table.on 'order.dt search.dt', -> 27 | table.column '.rank', 28 | search: 'applied' 29 | order: 'applied' 30 | .nodes().each (cell, i) -> 31 | cell.innerHTML = (i + 1) + '.' 32 | -------------------------------------------------------------------------------- /spec/models/issue_spec.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: issues 4 | # 5 | # id :integer not null, primary key 6 | # github_url :string(255) not null 7 | # number :integer not null 8 | # title :string(255) default("Untitled"), not null 9 | # issuer_id :integer not null 10 | # repository_id :integer not null 11 | # labels :text not null 12 | # body :text 13 | # assignee_id :integer 14 | # milestone :string(255) 15 | # opened_at :datetime not null 16 | # closed_at :datetime 17 | # created_at :datetime 18 | # updated_at :datetime 19 | # 20 | 21 | describe Issue do 22 | before :each do 23 | @issue = create :issue 24 | end 25 | 26 | it 'has a valid factory' do 27 | expect(create :issue).to be_valid 28 | end 29 | 30 | context 'with bounties' do 31 | before :each do 32 | @claimant = create :coder 33 | @bounty = create :bounty, issue: @issue 34 | @issue.assignee = @claimant 35 | end 36 | 37 | it 'claims bounties when closed' do 38 | @issue.close! 39 | @bounty.reload 40 | expect(@bounty.claimed_at).to be 41 | expect(@bounty.claimant).to eq(@claimant) 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /app/controllers/concerns/burndown_chart.rb: -------------------------------------------------------------------------------- 1 | class BurndownChart 2 | def initialize(issues) 3 | @issues = issues 4 | end 5 | 6 | def timeline 7 | option = { 8 | width: 1000, 9 | height: 440, 10 | colors: %w(orange green blue) 11 | } 12 | 13 | GoogleVisualr::Interactive::AnnotatedTimeLine.new(data_table, option) 14 | end 15 | 16 | private 17 | 18 | def data_table 19 | data_table = GoogleVisualr::DataTable.new 20 | 21 | data_table.new_column('date', 'Date') 22 | data_table.new_column('number', 'New issues') 23 | data_table.new_column('number', 'Closed issues') 24 | data_table.new_column('number', 'Open issues') 25 | 26 | data_table.add_rows(data) 27 | 28 | data_table 29 | end 30 | 31 | def full_date_range 32 | start_date = Time.zone.now.to_date 33 | unless @issues.order(:number).first.nil? 34 | start_date = @issues.order(:number).first.opened_at.to_date 35 | end 36 | start_date..Time.zone.now.to_date 37 | end 38 | 39 | def data 40 | open_issues = @issues.group('DATE(opened_at)').count 41 | closed_issues = @issues.closed.group('DATE(closed_at)').count 42 | 43 | data = [] 44 | open = 0 45 | full_date_range.each do |d| 46 | open += (open_issues[d] || 0) - (closed_issues[d] || 0) 47 | data << [d, open_issues[d] || 0, closed_issues[d] || 0, open] 48 | end 49 | 50 | data 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css.scss: -------------------------------------------------------------------------------- 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 vendor/assets/stylesheets of plugins, if any, 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 top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require_tree . 13 | *= require dataTables/bootstrap/3/jquery.dataTables.bootstrap 14 | */ 15 | @import "bootstrap-sprockets"; 16 | @import "bootstrap"; 17 | /*@import "font-awesome-sprockets";*/ 18 | @import "font-awesome"; 19 | 20 | #page-wrapper { 21 | margin-top: 50px; 22 | } 23 | 24 | .magnificent-top-padding{ 25 | padding-top: 30px; 26 | } 27 | 28 | .table:last-child { 29 | margin-bottom: 0px; 30 | } 31 | 32 | // DataTables tweaks: 33 | // Remove option to select amount of entries per page 34 | // and pull search box to the left 35 | .dataTables_wrapper { 36 | .col-xs-6:nth-child(1){ 37 | display: none; 38 | } 39 | } 40 | .dataTables_filter { 41 | float: left; 42 | margin-left: 20px; 43 | margin-top: 10px; 44 | 45 | input { 46 | margin-left: 10px; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /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 `rake 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 | development: 14 | secret_key_base: 964dfc965862690bd1575f8b8b4ae33f4aa88251dd642cc00173a0de0936879b78cdd142c6478602b8576b597b143e99c1c30522d1d18ff2af1a172b93c448b2 15 | github_token: #dev# 16 | github_app_id: '651692db8ef2d40913ff' 17 | github_app_secret: '85a54d91e1d436483930b0e00d3b21de73c6b016' 18 | 19 | test: 20 | secret_key_base: 9aaf264a8c9fe9d98e2892ae1adb8a46e294fd433f06b0230b3f1eece45761bdcb0e1abb37d8949ddb82e4b6a0eb46fc19960be8fb22893f2cfacfd3cd351694 21 | github_token: <%= ENV["GITHUB_TOKEN"] %> 22 | github_app_id: '651692db8ef2d40913ff' 23 | github_app_secret: '85a54d91e1d436483930b0e00d3b21de73c6b016' 24 | 25 | # Do not keep production secrets in the repository, 26 | # instead read values from the environment. 27 | production: 28 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 29 | github_token: #prod# 30 | github_app_id: #prod# 31 | github_app_secret: #prod# 32 | -------------------------------------------------------------------------------- /app/views/pages/faq.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

What is gamification?

3 |

Gamification is a webapp that will reward you for contributing to 4 | <%=Rails.application.config.organisation%>'s projects.

5 | 6 |

How do I get points?

7 |

When you push your commits to GitHub, Gamification will process 8 | your commits and reward you based on how many lines you added. 9 | You can also fix issues to get points (see Bounties).

10 | 11 |

What are bounties?

12 |

You can post a reward (a bounty) on GitHub issues you'd like to be fixed. 13 | When this issue is resolved, the assignee will automatically claim all associated 14 | bounties.

15 | 16 |

How do these points work?

17 |

We have two kinds of points: reward points and bounty points. 18 | When you commit or claim a bounty, you get rewarded equally 19 | in both types of points.

20 |
21 |

Reward points

22 |

These points can be traded in for goodies.

23 | 24 |

Bounty points

25 |

These points can be used to put bounties on issues you want to see fixed.

26 |
27 | 28 |

Why do bounty points degrade over time?

29 |

This is because there is a global limit of 30 | <%=Rails.application.config.total_bounty_value%> bounty points. 31 | When this limit is exceeded, all amounts of bounty points are rescaled.

32 |
33 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

We're sorry, but something went wrong.

54 |
55 |

If you are the application owner check the logs for more information.

56 | 57 | 58 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

The change you wanted was rejected.

54 |

Maybe you tried to change something you didn't have access to.

55 |
56 |

If you are the application owner check the logs for more information.

57 | 58 | 59 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

The page you were looking for doesn't exist.

54 |

You may have mistyped the address or the page may have moved.

55 |
56 |

If you are the application owner check the logs for more information.

57 | 58 | 59 | -------------------------------------------------------------------------------- /app/views/coders/show.html.erb: -------------------------------------------------------------------------------- 1 | <% if @coder.blank? %> 2 |
3 | This person does not exist. 4 |
5 | <% else %> 6 |
7 |
8 | 15 | 16 |
17 |
18 | <%= avatar @coder, size: '184' %> 19 |
20 |
21 | <%= format_score @coder.score %> points 22 |
23 |
<%= format_short_score @coder.commits.count %>
commits
24 |
<%= format_short_score @coder.additions %>
additions
25 |
<%= format_short_score @coder.deletions %>
deletions
26 |
27 |
28 |
29 |
30 | 31 |
32 | <%= render partial: 'repositories/scoreboard' %> 33 |
34 |
35 | <% end %> 36 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in 3 | # config/application.rb. 4 | 5 | # Host, to be used for routes and Action Mailer. 6 | config.x.host = 'localhost:3000' 7 | 8 | # In the development environment your application's code is reloaded on 9 | # every request. This slows down response time but is perfect for development 10 | # since you don't have to restart the web server when you make code changes. 11 | config.cache_classes = false 12 | 13 | # Do not eager load code on boot. 14 | config.eager_load = false 15 | 16 | # Show full error reports and disable caching. 17 | config.consider_all_requests_local = true 18 | config.action_controller.perform_caching = false 19 | 20 | # Don't care if the mailer can't send. 21 | config.action_mailer.raise_delivery_errors = false 22 | config.action_mailer.default_url_options = { host: config.x.host } 23 | 24 | # Print deprecation notices to the Rails logger. 25 | config.active_support.deprecation = :log 26 | 27 | # Raise an error on page load if there are pending migrations. 28 | config.active_record.migration_error = :page_load 29 | 30 | # Debug mode disables concatenation and preprocessing of assets. 31 | # This option may cause significant delays in view rendering with a large 32 | # number of complex assets. 33 | config.assets.debug = true 34 | 35 | # Adds additional error checking when serving assets at runtime. 36 | # Checks for improperly declared sprockets dependencies. 37 | # Raises helpful error messages. 38 | config.assets.raise_runtime_errors = true 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in 3 | # config/application.rb. 4 | 5 | # Host, to be used for routes and Action Mailer. 6 | config.x.host = 'localhost:3000' 7 | 8 | # The test environment is used exclusively to run your application's 9 | # test suite. You never need to work with it otherwise. Remember that 10 | # your test database is "scratch space" for the test suite and is wiped 11 | # and recreated between test runs. Don't rely on the data there! 12 | config.cache_classes = true 13 | 14 | # Do not eager load code on boot. This avoids loading your whole application 15 | # just for the purpose of running a single test. If you are using a tool that 16 | # preloads Rails for running tests, you may have to set it to true. 17 | config.eager_load = false 18 | 19 | # Configure static asset server for tests with Cache-Control for performance. 20 | config.serve_static_assets = true 21 | config.static_cache_control = 'public, max-age=3600' 22 | 23 | # Show full error reports and disable caching. 24 | config.consider_all_requests_local = true 25 | config.action_controller.perform_caching = false 26 | 27 | # Raise exceptions instead of rendering exception templates. 28 | config.action_dispatch.show_exceptions = false 29 | 30 | # Disable request forgery protection in test environment. 31 | config.action_controller.allow_forgery_protection = false 32 | 33 | # Tell Action Mailer not to deliver emails to the real world. 34 | # The :test delivery method accumulates sent emails in the 35 | # ActionMailer::Base.deliveries array. 36 | config.action_mailer.delivery_method = :test 37 | config.action_mailer.default_url_options = { host: config.x.host } 38 | 39 | # Print deprecation notices to the stderr. 40 | config.active_support.deprecation = :stderr 41 | 42 | # Raises error for missing translations 43 | # config.action_view.raise_on_missing_translations = true 44 | end 45 | -------------------------------------------------------------------------------- /app/controllers/webhooks_controller.rb: -------------------------------------------------------------------------------- 1 | class WebhooksController < ApplicationController 2 | skip_before_action :verify_authenticity_token 3 | 4 | def receive 5 | event = request.headers['X-Github-Event'] 6 | return head :bad_request unless event 7 | 8 | case event 9 | when 'push' 10 | push(request.request_parameters) 11 | when 'issues' 12 | issues(request.request_parameters) 13 | when 'repository' 14 | repository(request.request_parameters) 15 | else 16 | head :ok 17 | end 18 | end 19 | 20 | private 21 | 22 | def push(json) 23 | @owner = json['repository']['owner']['name'] 24 | repo = Repository.find_by(name: json['repository']['name']) 25 | return head :ok unless repo && valid_owner? 26 | 27 | repo.pull_or_clone 28 | 29 | if json['commits'] 30 | json['commits'].each do |commit| 31 | Commit.register_from_sha(repo, commit['id']) 32 | end 33 | end 34 | head :created 35 | end 36 | 37 | def issues(json) 38 | @owner = json['repository']['owner']['login'] 39 | repo = Repository.find_by(name: json['repository']['name']) 40 | return head :ok unless repo && valid_owner? 41 | 42 | issue = Issue.find_or_create_from_hash(json['issue'], repo) 43 | 44 | case json['action'] 45 | when 'opened', 'reopened' 46 | issue.update!(closed_at: nil) 47 | when 'closed' 48 | issue.close!(time: DateTime.rfc3339(json['issue']['closed_at'])) 49 | when 'assigned' 50 | assignee = Coder.find_by(github_name: json['issue']['assignee']['login']) 51 | issue.update!(assignee: assignee) 52 | when 'unassigned' 53 | issue.update!(assignee: nil) 54 | else 55 | return head :ok 56 | end 57 | head :created 58 | end 59 | 60 | def repository(json) 61 | repo = json['repository'] 62 | @owner = repo['owner']['login'] 63 | return head :ok unless RepoFilters.track?(repo) && valid_owner? 64 | 65 | case json['action'] 66 | when 'created' 67 | Repository.create_or_update(repo) 68 | return head :created 69 | end 70 | 71 | head :ok 72 | end 73 | 74 | def valid_owner? 75 | @owner == Rails.application.config.organisation 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /app/views/bounties/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <% if current_coder %> 4 |
    5 |
  • 6 | Remaining points     7 | 8 | <%= current_coder.bounty_residual %> 9 | 10 |
  • 11 |
12 | <% end %> 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | <% if current_coder %><% end %> 21 | 22 | 23 | 24 | <% @issues.each do |issue| %> 25 | <% repo = issue.repository %> 26 | 27 | 28 | 29 | 30 | <% if current_coder %> 31 | 47 | <% end %> 48 | 49 | <% end %> 50 | 51 |
RepoIssueTotal bountyMy bounty
<%= link_to repo.name, repo %> <%= link_to issue.title, issue.github_url %><%= format_score issue.total_bounty_value %> 32 |
33 | <%= form_for find_bounty(issue, current_coder), 34 | url: {action: 'update_or_create'}, 35 | method: :post, 36 | remote: true do |f| %> 37 | <%= f.hidden_field :issue_id %> 38 | <%= f.text_field :value, 39 | autocomplete: 'off', class: 'form-control text-right' %> 40 | 41 | <%= f.submit(value: 'Put', data: {disable_with: 'Saving'}, 42 | class: 'btn btn-default') %> 43 | 44 | <% end %> 45 |
46 |
52 |
53 | 54 |
55 |
56 | -------------------------------------------------------------------------------- /spec/github_jsons/ping.json: -------------------------------------------------------------------------------- 1 | { 2 | "zen": "Mind your words, they are important.", 3 | "hook_id": 4502899, 4 | "hook": { 5 | "url": "https://api.github.com/orgs/ZeusWPI/hooks/4502899", 6 | "ping_url": "https://api.github.com/orgs/ZeusWPI/hooks/4502899/pings", 7 | "id": 4502899, 8 | "name": "web", 9 | "active": true, 10 | "events": [ 11 | "create", 12 | "issues", 13 | "push" 14 | ], 15 | "config": { 16 | "url": "https://zeus.ugent.be/game/payload", 17 | "content_type": "json", 18 | "insecure_ssl": "0", 19 | "secret": "" 20 | }, 21 | "updated_at": "2015-04-03T13:56:13Z", 22 | "created_at": "2015-04-03T13:56:13Z" 23 | }, 24 | "organization": { 25 | "login": "ZeusWPI", 26 | "id": 331750, 27 | "url": "https://api.github.com/orgs/ZeusWPI", 28 | "repos_url": "https://api.github.com/orgs/ZeusWPI/repos", 29 | "events_url": "https://api.github.com/orgs/ZeusWPI/events", 30 | "members_url": "https://api.github.com/orgs/ZeusWPI/members{/member}", 31 | "public_members_url": "https://api.github.com/orgs/ZeusWPI/public_members{/member}", 32 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3", 33 | "description": null 34 | }, 35 | "sender": { 36 | "login": "Iasoon", 37 | "id": 6320308, 38 | "avatar_url": "https://avatars.githubusercontent.com/u/6320308?v=3", 39 | "gravatar_id": "", 40 | "url": "https://api.github.com/users/Iasoon", 41 | "html_url": "https://github.com/Iasoon", 42 | "followers_url": "https://api.github.com/users/Iasoon/followers", 43 | "following_url": "https://api.github.com/users/Iasoon/following{/other_user}", 44 | "gists_url": "https://api.github.com/users/Iasoon/gists{/gist_id}", 45 | "starred_url": "https://api.github.com/users/Iasoon/starred{/owner}{/repo}", 46 | "subscriptions_url": "https://api.github.com/users/Iasoon/subscriptions", 47 | "organizations_url": "https://api.github.com/users/Iasoon/orgs", 48 | "repos_url": "https://api.github.com/users/Iasoon/repos", 49 | "events_url": "https://api.github.com/users/Iasoon/events{/privacy}", 50 | "received_events_url": "https://api.github.com/users/Iasoon/received_events", 51 | "type": "User", 52 | "site_admin": false 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV['RAILS_ENV'] ||= 'test' 3 | require 'spec_helper' 4 | require File.expand_path('../../config/environment', __FILE__) 5 | require 'rspec/rails' 6 | # Add additional requires below this line. Rails is not loaded until this point! 7 | 8 | # Requires supporting ruby files with custom matchers and macros, etc, in 9 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 10 | # run as spec files by default. This means that files in spec/support that end 11 | # in _spec.rb will both be required and run as specs, causing the specs to be 12 | # run twice. It is recommended that you do not name files matching this glob to 13 | # end with _spec.rb. You can configure this pattern with the --pattern 14 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 15 | # 16 | # The following line is provided for convenience purposes. It has the downside 17 | # of increasing the boot-up time by auto-requiring all files in the support 18 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 19 | # require only the support files necessary. 20 | # 21 | # Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } 22 | 23 | # Checks for pending migrations before tests are run. 24 | # If you are not using ActiveRecord, you can remove this line. 25 | ActiveRecord::Migration.maintain_test_schema! 26 | 27 | RSpec.configure do |config| 28 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 29 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 30 | 31 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 32 | # examples within a transaction, remove the following line or assign false 33 | # instead of true. 34 | config.use_transactional_fixtures = true 35 | 36 | # RSpec Rails can automatically mix in different behaviours to your tests 37 | # based on their file location, for example enabling you to call `get` and 38 | # `post` in specs under `spec/controllers`. 39 | # 40 | # You can disable this behaviour by removing the line below, and instead 41 | # explicitly tag your specs with their type, e.g.: 42 | # 43 | # RSpec.describe UsersController, :type => :controller do 44 | # # ... 45 | # end 46 | # 47 | # The different available types are documented in the features, such as in 48 | # https://relishapp.com/rspec/rspec-rails/docs 49 | config.infer_spec_type_from_file_location! 50 | end 51 | -------------------------------------------------------------------------------- /app/models/issue.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: issues 4 | # 5 | # id :integer not null, primary key 6 | # github_url :string(255) not null 7 | # number :integer not null 8 | # title :string(255) default("Untitled"), not null 9 | # issuer_id :integer not null 10 | # repository_id :integer not null 11 | # labels :text not null 12 | # body :text 13 | # assignee_id :integer 14 | # milestone :string(255) 15 | # opened_at :datetime not null 16 | # closed_at :datetime 17 | # created_at :datetime 18 | # updated_at :datetime 19 | # 20 | 21 | class Issue < ActiveRecord::Base 22 | extend Datenfisch::Model 23 | belongs_to :issuer, class_name: :Coder, 24 | inverse_of: :created_issues, 25 | foreign_key: 'issuer_id' 26 | belongs_to :assignee, inverse_of: :assigned_issues, 27 | class_name: 'Coder' 28 | belongs_to :repository 29 | has_many :bounties 30 | has_many :unclaimed_bounties, -> { where claimed_at: nil }, 31 | class_name: 'Bounty' 32 | 33 | scope :open, -> { where(closed_at: nil) } 34 | scope :closed, -> { where.not(closed_at: nil) } 35 | 36 | serialize :labels 37 | 38 | include Schwarm 39 | stat :absolute_bounty_value, BountyFisch.absolute_bounty_value 40 | 41 | def total_bounty_value 42 | BountyPoints.bounty_points_from_abs(absolute_bounty_value) 43 | end 44 | 45 | def close!(time: Time.zone.now) 46 | bounties.where(claimed_at: nil).find_each(&:claim!) 47 | update!(closed_at: time) 48 | save! 49 | end 50 | 51 | def self.find_or_create_from_hash(json, repo) 52 | issuer = Coder.find_or_create_by_github_name(json['user']['login']) 53 | Issue.find_or_create_by(number: json['number'], repository: repo) do |issue| 54 | issue.github_url = json['html_url'] 55 | issue.number = json['number'] 56 | issue.title = json['title'] 57 | issue.body = json['body'] 58 | issue.issuer = issuer 59 | issue.labels = (json['labels'] || []).map { |label| label['name'] } 60 | issue.milestone = json['milestone'].try(:[], :title) 61 | issue.opened_at = DateTime.rfc3339(json['created_at']) 62 | 63 | closed_at = json['closed_at'] 64 | issue.closed_at = DateTime.rfc3339(closed_at) if closed_at 65 | 66 | unless json['assignee'].blank? 67 | issue.assignee = 68 | Coder.find_or_create_by_github_name(json['assignee']['login']) 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /app/assets/stylesheets/top4.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the top4 controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | 5 | .top4.toprow { 6 | height: 300px; 7 | } 8 | 9 | div.top4 { 10 | margin: auto; 11 | .panel { 12 | height: 100%; 13 | } 14 | 15 | #top-coders { 16 | td { 17 | vertical-align: middle; 18 | } 19 | 20 | .rank { 21 | font-size: 20pt; 22 | text-align: right; 23 | width: 45px; 24 | height: 45px; 25 | } 26 | .avatar { 27 | width: 45px; 28 | img { 29 | border-radius: 50%; 30 | } 31 | } 32 | .username { 33 | font-weight: bold; 34 | } 35 | .score { 36 | text-align: right; 37 | font-size: 20pt; 38 | } 39 | 40 | tr:first-child { 41 | width: 90px; 42 | height: 90px; 43 | .name { 44 | font-size: 15pt; 45 | } 46 | .username { 47 | font-size: 23pt; 48 | } 49 | .avatar { 50 | width: 120px; 51 | 52 | img { 53 | position: absolute; 54 | top: -40px; 55 | left: 30px; 56 | border-radius: 50%; 57 | box-shadow: 0 0 0 3px #fff, 0 0 0 4px #999, 0 2px 5px 4px rgba(0,0,0,.2); 58 | } 59 | } 60 | .score { 61 | font-size: 25pt; 62 | } 63 | } 64 | 65 | } 66 | 67 | #top-repos { 68 | width: 100%; 69 | max-height: 100%; 70 | display: block; 71 | margin: auto; 72 | padding: 10px; 73 | 74 | vertical-align: bottom; 75 | shape-rendering: crisp-edges; 76 | image-rendering: crisp-edges; 77 | 78 | .repo-bar { 79 | fill: #1565c0; 80 | } 81 | 82 | .repo-label { 83 | text-anchor: middle; 84 | font: 16px sans-serif; 85 | fill: black; 86 | } 87 | 88 | .contributor-name { 89 | text-anchor: middle; 90 | fill: white; 91 | } 92 | 93 | .axis { 94 | text { 95 | font: 16px sans-serif; 96 | fill: black; 97 | } 98 | } 99 | } 100 | } 101 | 102 | td.truncatable { 103 | text-overflow: ellipsis; 104 | overflow: hidden; 105 | max-width: 1px; 106 | } 107 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Gamification 6 | <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> 7 | <%= javascript_include_tag "application", "data-turbolinks-track" => true %> 8 | <%# Google charts dependency %> 9 | <%= javascript_include_tag "//www.google.com/jsapi" %> 10 | <%= csrf_meta_tags %> 11 | 12 | 13 | 14 |
15 | 49 | 50 |
51 |
52 | <% flash.each do |type, message| %> 53 |
54 | 55 | <%= message %> 56 |
57 | <% end %> 58 |
59 | 60 | <%= yield %> 61 |
62 |
63 | 64 | 65 | -------------------------------------------------------------------------------- /app/models/commit.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: commits 4 | # 5 | # id :integer not null, primary key 6 | # coder_id :integer 7 | # repository_id :integer 8 | # sha :string(255) not null 9 | # additions :integer default(0), not null 10 | # deletions :integer default(0), not null 11 | # date :datetime not null 12 | # created_at :datetime 13 | # updated_at :datetime 14 | # 15 | 16 | class Commit < ActiveRecord::Base 17 | belongs_to :coder 18 | belongs_to :repository 19 | 20 | require 'rugged' 21 | 22 | scope :additions, -> { sum :additions } 23 | scope :deletions, -> { sum :deletions } 24 | 25 | def reward! 26 | coder.reward_commit!(self) 27 | end 28 | 29 | def self.register_from_sha(repo, sha, **opts) 30 | r_commit = repo.rugged_repo.lookup(sha) 31 | register_rugged(repo, r_commit, **opts) 32 | end 33 | 34 | def self.register_rugged(repo, r_commit, reward: true) 35 | # if committer can't be determined, fuck this shit 36 | committer = get_committer(repo, r_commit) 37 | return nil unless committer 38 | Commit.find_or_create_by(repository: repo, 39 | sha: r_commit.oid, 40 | coder: committer) do |commit| 41 | commit.initialize_from_rugged(r_commit) 42 | commit.reward! if reward 43 | end 44 | end 45 | 46 | def self.get_committer(repo, r_commit) 47 | identity = GitIdentity.find_by(name: r_commit.committer[:name], 48 | email: r_commit.committer[:email]) 49 | unless identity 50 | coder = get_committer_from_github(repo, r_commit) 51 | return nil unless coder 52 | identity = GitIdentity.create(name: r_commit.committer[:name], 53 | email: r_commit.committer[:email], 54 | coder: coder) 55 | end 56 | identity.coder 57 | end 58 | 59 | def self.get_committer_from_github(repo, r_commit) 60 | github = Rails.configuration.x.github 61 | commit = github.repos.commits.get(Rails.application.config.organisation, 62 | repo.name, r_commit.oid) 63 | if commit.committer 64 | Coder.find_or_create_by_github_name(commit.committer.login) 65 | end 66 | end 67 | 68 | def initialize_from_rugged(r_commit) 69 | self.date = r_commit.time 70 | set_stats(r_commit) 71 | end 72 | 73 | private 74 | 75 | def set_stats(r_commit) # rubocop:disable Style/AccessorMethodName 76 | if r_commit.parents.size >= 2 77 | # Do not reward merges 78 | self.additions = self.deletions = 0 79 | else 80 | if r_commit.parents.empty? 81 | # First commit 82 | diff = r_commit.diff 83 | else 84 | diff = r_commit.diff(r_commit.parents.first) 85 | end 86 | self.additions = diff.stat[2] 87 | self.deletions = diff.stat[1] 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /app/models/coder.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: coders 4 | # 5 | # id :integer not null, primary key 6 | # github_name :string(255) not null 7 | # full_name :string(255) default(""), not null 8 | # avatar_url :string(255) not null 9 | # github_url :string(255) not null 10 | # reward_residual :integer default(0), not null 11 | # absolute_bounty_residual :integer default(0), not null 12 | # other_score :integer default(0), not null 13 | # created_at :datetime 14 | # updated_at :datetime 15 | # 16 | 17 | class Coder < ActiveRecord::Base 18 | extend FriendlyId 19 | extend Datenfisch::Model 20 | friendly_id :github_name 21 | 22 | devise :omniauthable, omniauth_providers: [:github] 23 | 24 | has_many :created_issues, class_name: 'Issue', foreign_key: 'issuer_id' 25 | has_many :assigned_issues, class_name: 'Issue', foreign_key: 'assignee_id' 26 | has_many :claimed_bounties, class_name: 'Bounty', foreign_key: 'claimant_id' 27 | has_many :bounties 28 | has_many :commits 29 | 30 | after_commit :clear_caches 31 | after_rollback :clear_caches 32 | 33 | include Schwarm 34 | stat :additions, CommitFisch.additions 35 | stat :deletions, CommitFisch.deletions 36 | stat :commit_count, CommitFisch.count 37 | stat :changed_lines, additions + deletions 38 | stat :claimed_value, BountyFisch.claimed_value 39 | stat :addition_score, CommitFisch.addition_score 40 | stat :score, addition_score + claimed_value 41 | 42 | def reward_bounty!(bounty) 43 | self.bounty_residual += bounty.value 44 | self.reward_residual += bounty.value 45 | save! 46 | end 47 | 48 | def refund_bounty!(bounty) 49 | self.bounty_residual += bounty.value 50 | save! 51 | end 52 | 53 | def reward_commit!(commit) 54 | addition_score = (Math.log(commit.additions + 1) * 55 | Rails.application.config.addition_score_factor).round 56 | self.reward_residual += addition_score 57 | self.bounty_residual += addition_score 58 | save! 59 | end 60 | 61 | def bounty_residual 62 | BountyPoints.bounty_points_from_abs(absolute_bounty_residual) 63 | end 64 | 65 | def bounty_residual=(new_value) 66 | self.absolute_bounty_residual = 67 | BountyPoints.bounty_points_to_abs(new_value) 68 | end 69 | 70 | def self.from_omniauth(auth) 71 | find_by_github_name(auth.info.nickname) 72 | end 73 | 74 | def self.find_or_create_by_github_name(name) 75 | Coder.find_or_create_by(github_name: name) do |coder| 76 | # Fetch data from github 77 | data = Rails.configuration.x.github.users.get(user: name) 78 | coder.full_name = data.try(:name) || '' 79 | coder.avatar_url = data.avatar_url 80 | coder.github_url = data.html_url 81 | end 82 | end 83 | 84 | private 85 | 86 | def clear_caches 87 | BountyPoints.expire_coder_bounty_points 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 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 | 9 | module Gamification 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified 12 | # here. Application configuration should go into files in 13 | # config/initializers -- all .rb files in that directory are automatically 14 | # loaded. 15 | 16 | # Set Time.zone default to the specified zone and make Active Record 17 | # auto-convert to this zone. Run "rake -D time" for a list of tasks for 18 | # finding time zone names. Default is UTC. 19 | config.time_zone = 'Brussels' 20 | # autoload lib files 21 | config.autoload_paths << Rails.root.join('lib') 22 | 23 | # The default locale is :en and all translations from 24 | # config/locales/*.rb,yml are auto loaded. 25 | # config.i18n.load_path += 26 | # Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 27 | # config.i18n.default_locale = :de 28 | 29 | # Organisation to track 30 | config.organisation = 'ZeusWPI' 31 | 32 | config.repository_filters = { 33 | # only: [ 'gamification', 'Haldis' ] 34 | except: [ 35 | 'glowing-octo-dubstep', 36 | 'VPW-voorbereiding-2015', 37 | 'VPW-voorbereiding-2014', 38 | 'contests', 39 | 'Bestuurstaakjes', 40 | 'SumoRoboComp', 41 | 'kaggle-rta', 42 | 'manage-user', 43 | 'website-manage', 44 | 'errbit' 45 | ] 46 | # private: false 47 | } 48 | 49 | # Total bounty value 50 | config.total_bounty_value = 5000 51 | 52 | # Addition score factor 53 | config.addition_score_factor = 10 54 | 55 | config.generators do |g| 56 | g.test_framework :rspec, 57 | fixtures: true, 58 | view_specs: false, 59 | helper_specs: false, 60 | routing_specs: false, 61 | controller_specs: true, 62 | request_specs: true 63 | g.fixture_replacement :factory_girl, dir: 'spec/factories' 64 | end 65 | 66 | # Backport from Rails 4.2: custom configurations 67 | # Just remove this block when upgrading from 4.1.8 to 4.2 68 | if Rails.version == '4.1.8' 69 | class Custom 70 | def initialize 71 | @configurations = {} 72 | end 73 | 74 | def method_missing(method, *args) 75 | if method =~ /=$/ 76 | @configurations[$`.to_sym] = args.first 77 | else 78 | @configurations.fetch(method) do 79 | @configurations[method] = ActiveSupport::OrderedOptions.new 80 | end 81 | end 82 | end 83 | end 84 | 85 | config.x = Custom.new 86 | else 87 | warn('Remove this backport at config/application:62-84') 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /app/models/repository.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: repositories 4 | # 5 | # id :integer not null, primary key 6 | # name :string(255) not null 7 | # github_url :string(255) not null 8 | # clone_url :string(255) not null 9 | # created_at :datetime 10 | # updated_at :datetime 11 | # 12 | 13 | class Repository < ActiveRecord::Base 14 | extend FriendlyId 15 | extend Datenfisch::Model 16 | friendly_id :name 17 | 18 | has_many :issues 19 | has_many :commits 20 | has_many :bounties, through: :issues 21 | has_many :coders, -> { uniq }, through: :commits 22 | 23 | validates :name, presence: true 24 | 25 | include Schwarm 26 | stat :additions, CommitFisch.additions 27 | stat :deletions, CommitFisch.deletions 28 | stat :commit_count, CommitFisch.count 29 | stat :changed_lines, additions + deletions 30 | stat :bounty_score, BountyFisch.claimed_value 31 | stat :score, CommitFisch.addition_score + bounty_score 32 | 33 | require 'rugged' 34 | 35 | def pull_or_clone 36 | if Dir.exist?(path) 37 | ensure_correct_remote_url 38 | 39 | logger.info("Fetching #{name}...") 40 | `cd #{path} && git fetch && git reset --hard origin/master` 41 | else 42 | logger.info("Cloning #{name}...") 43 | `mkdir -p #{path} && git clone #{authenticated_clone_url} #{path}` 44 | end 45 | end 46 | 47 | def register_commits! 48 | r_repo = rugged_repo 49 | walker = Rugged::Walker.new(r_repo) 50 | # Push all heads 51 | r_repo.branches.each { |b| walker.push b.target_id } 52 | walker.push(r_repo.last_commit) 53 | walker.each do |commit| 54 | Commit.register_rugged(self, commit, reward: false) 55 | end 56 | end 57 | 58 | def fetch_issues! 59 | github = Rails.configuration.x.github 60 | github.issues.list(user: Rails.application.config.organisation, 61 | repo: name, state: 'all', filter: 'all').each do |hash| 62 | Issue.find_or_create_from_hash(hash, self) 63 | end 64 | end 65 | 66 | require 'rugged' 67 | def rugged_repo 68 | Rugged::Repository.new(path) 69 | end 70 | 71 | def full_name 72 | "#{Rails.application.config.organisation}/#{name}" 73 | end 74 | 75 | def self.create_or_update(repo_hash) 76 | repo = Repository.find_or_create_by(name: repo_hash['name']) do |r| 77 | r.github_url = repo_hash['html_url'] 78 | r.clone_url = repo_hash['clone_url'] 79 | end 80 | repo.pull_or_clone 81 | repo.register_commits! 82 | repo.fetch_issues! 83 | end 84 | 85 | private 86 | 87 | def path 88 | "#{Rails.root}/repos/#{name}" 89 | end 90 | 91 | def authenticated_clone_url 92 | clone_url.sub('https://') do 93 | $& + Rails.application.secrets.github_token + '@' 94 | end 95 | end 96 | 97 | def ensure_correct_remote_url 98 | logger.info("Checking remote url of #{name}...") 99 | remote_url = `cd #{path} && git remote -v`.split[1] 100 | return if remote_url == authenticated_clone_url 101 | 102 | logger.info('Setting new remote url...') 103 | `cd #{path} && git remote set-url origin #{authenticated_clone_url}` 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 4 | gem 'rails', '~> 4.1' 5 | 6 | # Use SCSS for stylesheets 7 | gem 'sass-rails', '~> 4.0.0' 8 | 9 | # Use Bootstrap for styling 10 | gem 'bootstrap-sass', '~> 3.3.1' 11 | 12 | # Use Font Awesome for icons 13 | gem 'font-awesome-rails' 14 | 15 | # Use Uglifier as compressor for JavaScript assets 16 | gem 'uglifier', '>= 1.3.0' 17 | 18 | # Use CoffeeScript for .js.coffee assets and views 19 | gem 'coffee-rails', '~> 4.0.0' 20 | 21 | # Airbrake 22 | gem 'airbrake' 23 | 24 | # Coveralls! 25 | gem 'coveralls', require: false 26 | 27 | # See https://github.com/sstephenson/execjs#readme for more supported runtimes 28 | # gem 'therubyracer', platforms: :ruby 29 | # Use jquery as the JavaScript library 30 | gem 'jquery-rails' 31 | 32 | # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks 33 | gem 'turbolinks' 34 | 35 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 36 | gem 'jbuilder', '~> 1.2' 37 | 38 | # Build google charts 39 | gem 'google_visualr' 40 | 41 | group :doc do 42 | # bundle exec rake doc:rails generates the API under doc/api. 43 | gem 'sdoc', require: false 44 | end 45 | 46 | group :test, :development do 47 | gem 'pry-byebug' 48 | end 49 | 50 | group :test do 51 | gem 'rack-test' 52 | gem 'rspec-rails' 53 | gem 'factory_girl_rails' 54 | gem 'faker' 55 | end 56 | 57 | group :development do 58 | gem 'capistrano' 59 | gem 'capistrano-rails' 60 | gem 'capistrano-passenger' 61 | gem 'capistrano-rvm' 62 | gem 'capistrano-rbenv' 63 | gem 'capistrano-rails-collection' 64 | 65 | gem 'annotate' 66 | 67 | gem 'rubocop', github: 'bbatsov/rubocop' 68 | end 69 | 70 | group :production do 71 | gem 'mysql2' 72 | end 73 | 74 | # Use ActiveModel has_secure_password 75 | # gem 'bcrypt-ruby', '~> 3.1.2' 76 | 77 | # Use unicorn as the app server 78 | # gem 'unicorn' 79 | 80 | # Use debugger 81 | # gem 'debugger', group: [:development, :test] 82 | 83 | # Use GitHub API (https://github.com/peter-murach/github) 84 | gem 'github_api' 85 | # Use Rugged 86 | gem 'rugged' 87 | 88 | # Use Devise (https://github.com/plataformatec/devise) 89 | gem 'devise' 90 | 91 | # Use OmniAuth (https://github.com/intridea/omniauth) 92 | gem 'omniauth' 93 | # Use GitHub provider for OmniAuth (https://github.com/intridea/omniauth-github) 94 | gem 'omniauth-github' 95 | 96 | # Use FriendlyId to have nice-looking URLs 97 | # (https://github.com/norman/friendly_id) 98 | gem 'friendly_id' 99 | 100 | # Use Turbolinks in a sane way (https://github.com/kossnocorp/jquery.turbolinks) 101 | gem 'jquery-turbolinks' 102 | 103 | # Use datenfisch (https://github.com/Iasoon/datenfisch.git) 104 | gem 'datenfisch', git: 'git://github.com/Iasoon/datenfisch.git', 105 | ref: '4f39bb3686b5facfb2552fe186d568ce3d259993' 106 | 107 | # Use jQuery plugin datatables (https://github.com/DataTables/DataTables) 108 | gem 'jquery-datatables-rails' 109 | 110 | # Use d3 for fancy visualisations 111 | gem 'd3-rails' 112 | 113 | # High voltage static pages 114 | gem 'high_voltage' 115 | 116 | # Use HTTParty for easier HTTP requests 117 | gem 'httparty' 118 | -------------------------------------------------------------------------------- /config/initializers/friendly_id.rb: -------------------------------------------------------------------------------- 1 | # FriendlyId Global Configuration 2 | # 3 | # Use this to set up shared configuration options for your entire application. 4 | # Any of the configuration options shown here can also be applied to single 5 | # models by passing arguments to the `friendly_id` class method or defining 6 | # methods in your model. 7 | # 8 | # To learn more, check out the guide: 9 | # 10 | # http://norman.github.io/friendly_id/file.Guide.html 11 | 12 | FriendlyId.defaults do |config| 13 | # ## Reserved Words 14 | # 15 | # Some words could conflict with Rails's routes when used as slugs, or are 16 | # undesirable to allow as slugs. Edit this list as needed for your app. 17 | config.use :reserved 18 | 19 | config.reserved_words = %w(new edit index session login logout users admin 20 | stylesheets assets javascripts images) 21 | 22 | # ## Friendly Finders 23 | # 24 | # Uncomment this to use friendly finders in all models. By default, if 25 | # you wish to find a record by its friendly id, you must do: 26 | # 27 | # MyModel.friendly.find('foo') 28 | # 29 | # If you uncomment this, you can do: 30 | # 31 | # MyModel.find('foo') 32 | # 33 | # This is significantly more convenient but may not be appropriate for 34 | # all applications, so you must explicity opt-in to this behavior. You can 35 | # always also configure it on a per-model basis if you prefer. 36 | # 37 | # Something else to consider is that using the :finders addon boosts 38 | # performance because it will avoid Rails-internal code that makes runtime 39 | # calls to `Module.extend`. 40 | # 41 | # config.use :finders 42 | # 43 | # ## Slugs 44 | # 45 | # Most applications will use the :slugged module everywhere. If you wish 46 | # to do so, uncomment the following line. 47 | # 48 | # config.use :slugged 49 | # 50 | # By default, FriendlyId's :slugged addon expects the slug column to be named 51 | # 'slug', but you can change it if you wish. 52 | # 53 | # config.slug_column = 'slug' 54 | # 55 | # When FriendlyId can not generate a unique ID from your base method, it 56 | # appends a UUID, separated by a single dash. You can configure the character 57 | # used as the separator. If you're upgrading from FriendlyId 4, you may wish 58 | # to replace this with two dashes. 59 | # 60 | # config.sequence_separator = '-' 61 | # 62 | # ## Tips and Tricks 63 | # 64 | # ### Controlling when slugs are generated 65 | # 66 | # As of FriendlyId 5.0, new slugs are generated only when the slug field is 67 | # nil, but if you're using a column as your base method can change this 68 | # behavior by overriding the `should_generate_new_friendly_id` method that 69 | # FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave 70 | # more like 4.0. 71 | # 72 | # config.use Module.new { 73 | # def should_generate_new_friendly_id? 74 | # slug.blank? || _changed? 75 | # end 76 | # } 77 | # 78 | # FriendlyId uses Rails's `parameterize` method to generate slugs, but for 79 | # languages that don't use the Roman alphabet, that's not usually suffient. 80 | # Here we use the Babosa library to transliterate Russian Cyrillic slugs to 81 | # ASCII. If you use this, don't forget to add "babosa" to your Gemfile. 82 | # 83 | # config.use Module.new { 84 | # def normalize_friendly_id(text) 85 | # text.to_slug.normalize! :transliterations => [:russian, :latin] 86 | # end 87 | # } 88 | end 89 | -------------------------------------------------------------------------------- /app/views/top4/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 7 | 8 |
9 |
10 |
11 | 12 | 13 | <% @coders.each_with_index do |coder, index| %> 14 | 15 | 20 | 23 | 30 | 31 | 32 | <% end %> 33 | 34 |
16 | <% if index != 0 %> 17 | <%= index + 1 %>. 18 | <% end %> 19 | 21 | <%= avatar coder, size: (index == 0 ? '120' : '45') %> 22 | 24 | <%= link_to coder.github_name, coder_path(coder), class: "username" %> 25 |
26 | <% unless coder.full_name.blank? %> 27 | <%= coder.full_name %> 28 | <% end %> 29 |
<%= format_score coder.score %>
35 |
36 |
37 |
38 |
39 | 40 |
41 |
42 |
43 | 44 |
45 |
46 |

New issues

47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | <%= render partial: 'issue', collection: @new_issues %> 56 | 57 |
58 |
59 |
60 |
61 |

Largest bounties

62 |
63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | <%= render partial: 'issue', collection: @top_issues %> 71 | 72 |
73 |
74 |
75 |
76 |

Recently closed

77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | <%= render partial: 'issue_closed', collection: @closed_issues %> 86 | 87 |
88 |
89 |
90 |
91 | 92 |
93 |
94 | 95 | <%= javascript_tag do %> 96 | var repos = <%= raw @top_repos.to_json(methods: :base_uri) %>, 97 | contributors = <%= raw @repo_contributors.to_json %>; 98 | <% end %> 99 | -------------------------------------------------------------------------------- /spec/requests/webhooks_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe 'Webhooks', type: :request do 2 | def post_payload(json, type) 3 | post '/payload', json, 'Content-Type' => 'application/json', 4 | 'X-Github-Event' => type 5 | end 6 | 7 | it 'should respond to ping' do 8 | json = File.read('spec/github_jsons/ping.json') 9 | post_payload json, 'ping' 10 | end 11 | 12 | context 'with tracked repository' do 13 | before :all do 14 | Rails.application.config.organisation = 'ZeusWPI' 15 | Rails.application.config.repository_filters = {} 16 | end 17 | 18 | context 'received created-repo webhook' do 19 | before :each do 20 | json = File.read('spec/github_jsons/repo_create.json') 21 | post_payload json, 'repository' 22 | end 23 | 24 | it 'creates a repository' do 25 | expect(Repository.count).to eq(1) 26 | end 27 | 28 | it 'sets correct name' do 29 | expect(Repository.find_by name: 'glowing-octo-dubstep').to be 30 | end 31 | end 32 | 33 | # Fake rugged implementation 34 | class FakeRepo 35 | def lookup(sha) 36 | FakeCommit.new sha 37 | end 38 | end 39 | 40 | class FakeCommit 41 | attr_reader :oid, :time, :parents 42 | 43 | def initialize(sha) 44 | @oid = sha 45 | @parents = ['lol'] 46 | @time = Time.zone.now 47 | end 48 | 49 | def diff(_arg = nil) 50 | FakeDiff.new 51 | end 52 | end 53 | 54 | class FakeDiff 55 | def stat 56 | [0, 10, 20] 57 | end 58 | end 59 | 60 | context 'received push webhook' do 61 | before :each do 62 | @god = create :repository, name: 'glowing-octo-dubstep' 63 | @iasoon = create :coder, github_name: 'Iasoon' 64 | @sha = 'be981ec9e53ef639361ffebbf88845c711fb07bd' # From json 65 | 66 | RSpec::Mocks.with_temporary_scope do 67 | allow(Commit).to receive(:get_committer) { @iasoon } 68 | allow_any_instance_of(Repository).to receive(:rugged_repo) { FakeRepo.new } 69 | allow_any_instance_of(Repository).to receive(:pull_or_clone) 70 | json = File.read('spec/github_jsons/push.json') 71 | post_payload json, 'push' 72 | end 73 | end 74 | 75 | it 'registers the commit' do 76 | expect(Commit.find_by repository: @god, sha: @sha).to be 77 | end 78 | 79 | it 'rewarded the commit' do 80 | @iasoon.reload 81 | expect(@iasoon.reward_residual).to be > 0 82 | end 83 | end 84 | 85 | context 'received issue webhook' do 86 | before :each do 87 | @god = create :repository, name: 'glowing-octo-dubstep' 88 | json = File.read('spec/github_jsons/issue_open.json') 89 | post_payload json, 'issues' 90 | end 91 | 92 | it 'creates a new issue' do 93 | expect(@god.issues.count).to eq(1) 94 | end 95 | 96 | context 'closed' do 97 | before :each do 98 | json = File.read('spec/github_jsons/issue_close.json') 99 | post_payload json, 'issues' 100 | end 101 | 102 | it 'closes the issue' do 103 | expect(@god.issues.where.not(closed_at: nil).count).to eq(1) 104 | end 105 | end 106 | end 107 | end 108 | 109 | context 'with untracked repository' do 110 | before :all do 111 | Rails.application.config.organisation = 'Derps Inc' 112 | end 113 | 114 | context 'received created-repo webhook' do 115 | before :each do 116 | json = File.read('spec/github_jsons/repo_create.json') 117 | post_payload json, 'repository' 118 | end 119 | 120 | it 'did not create a repository' do 121 | expect(Repository.count).to eq(0) 122 | end 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /app/models/bounty.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: bounties 4 | # 5 | # id :integer not null, primary key 6 | # absolute_value :integer not null 7 | # issue_id :integer not null 8 | # issuer_id :integer not null 9 | # claimant_id :integer 10 | # claimed_value :integer 11 | # claimed_at :datetime 12 | # created_at :datetime 13 | # updated_at :datetime 14 | # 15 | 16 | class Bounty < ActiveRecord::Base 17 | belongs_to :issue 18 | has_one :repository, through: :issue 19 | belongs_to :issuer, class_name: 'Coder', foreign_key: 'issuer_id' 20 | belongs_to :claimant, class_name: 'Coder', foreign_key: 'claimant_id' 21 | 22 | validates :issue, presence: true 23 | validates :issuer, presence: true 24 | validates :absolute_value, presence: true, numericality: { 25 | only_integer: true, 26 | greater_than_or_equal_to: 0 27 | } 28 | 29 | after_commit :clear_caches 30 | after_rollback :clear_caches 31 | 32 | delegate :to_s, to: :value 33 | 34 | def self.update_or_create(issue, coder, new_value) 35 | # Find the bounty for this issue if it already exists, or make a new one 36 | bounty = Bounty.find_or_create_by(issue: issue, 37 | issuer: coder, 38 | claimed_at: nil) do |b| 39 | b.absolute_value = 0 40 | end 41 | 42 | bounty.update_value!(new_value) 43 | end 44 | 45 | def update_value!(new_value) 46 | # Check whether the user has got enought points to spend 47 | delta = new_value - value 48 | if delta > issuer.bounty_residual 49 | fail Error, "You don\'t have enough bounty points to put a bounty of"\ 50 | ' this amount.' 51 | end 52 | 53 | self.value += delta 54 | 55 | issuer.bounty_residual -= delta 56 | 57 | # Try to save the bounty, update the remaining bounty points, and return 58 | # some possibly updated records 59 | # It is important that this happens at the same time, so 60 | # BountyPoints.total_bounty_points stays the same! 61 | transaction do 62 | unless save 63 | fail Error, 'There occured an error while trying to save your bounty'\ 64 | " (#{bounty.errors.full_messages})" 65 | end 66 | 67 | unless issuer.save 68 | fail Error, 'There occured an error while trying to adjust your'\ 69 | ' remaining bounty points'\ 70 | " (#{bounty.errors.full_messages})" 71 | end 72 | end 73 | 74 | PublishBounty.new(self).call 75 | end 76 | 77 | def claim!(time: Time.zone.now) 78 | return if claimed_at # This bounty has already been claimed 79 | if issue.assignee && issue.assignee != issuer 80 | # Reward assignee 81 | pinpoint_value!(coder: issue.assignee, time: time) 82 | issue.assignee.reward_bounty!(self) 83 | else 84 | # Refund bounty points 85 | issuer.refund_bounty!(self) 86 | # This bounty is of no use; destroy it. 87 | destroy! 88 | end 89 | end 90 | 91 | def value 92 | claimed_value || BountyPoints.bounty_points_from_abs(absolute_value) 93 | end 94 | 95 | def value=(new_value) 96 | if claimed_at 97 | fail Error, 'Trying to set a new value to an already claimed bounty!' 98 | end 99 | self.absolute_value = BountyPoints.bounty_points_to_abs(new_value) 100 | end 101 | 102 | def pinpoint_value!(coder: nil, time: Time.current) 103 | self.claimant = coder 104 | self.claimed_at = time 105 | self.claimed_value = self.value 106 | self.absolute_value = 0 107 | save! 108 | end 109 | 110 | class Error < StandardError 111 | end 112 | 113 | private 114 | 115 | def clear_caches 116 | BountyPoints.expire_assigned_bounty_points 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/sb-admin.css: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Start Bootstrap - http://startbootstrap.com 3 | 'SB Admin' HTML Template by Start Bootstrap 4 | 5 | All Start Bootstrap themes are licensed under Apache 2.0. 6 | For more info and more free Bootstrap 3 HTML themes, visit http://startbootstrap.com! 7 | */ 8 | 9 | /* ATTN: This is mobile first CSS - to update 786px and up screen width use the media query near the bottom of the document! */ 10 | 11 | /* Global Styles */ 12 | 13 | body { 14 | margin-top: 50px; 15 | } 16 | 17 | #wrapper { 18 | padding-left: 0; 19 | } 20 | 21 | #page-wrapper { 22 | width: 100%; 23 | padding: 5px 15px; 24 | } 25 | 26 | /* Nav Messages */ 27 | 28 | .messages-dropdown .dropdown-menu .message-preview .avatar, 29 | .messages-dropdown .dropdown-menu .message-preview .name, 30 | .messages-dropdown .dropdown-menu .message-preview .message, 31 | .messages-dropdown .dropdown-menu .message-preview .time { 32 | display: block; 33 | } 34 | 35 | .messages-dropdown .dropdown-menu .message-preview .avatar { 36 | float: left; 37 | margin-right: 15px; 38 | } 39 | 40 | .messages-dropdown .dropdown-menu .message-preview .name { 41 | font-weight: bold; 42 | } 43 | 44 | .messages-dropdown .dropdown-menu .message-preview .message { 45 | font-size: 12px; 46 | } 47 | 48 | .messages-dropdown .dropdown-menu .message-preview .time { 49 | font-size: 12px; 50 | } 51 | 52 | 53 | /* Nav Announcements */ 54 | 55 | .announcement-heading { 56 | font-size: 50px; 57 | margin: 0; 58 | } 59 | 60 | .announcement-text { 61 | margin: 0; 62 | } 63 | 64 | /* Table Headers */ 65 | 66 | table.tablesorter thead { 67 | cursor: pointer; 68 | } 69 | 70 | table.tablesorter thead tr th:hover { 71 | background-color: #f5f5f5; 72 | } 73 | 74 | /* Flot Chart Containers */ 75 | 76 | .flot-chart { 77 | display: block; 78 | height: 400px; 79 | } 80 | 81 | .flot-chart-content { 82 | width: 100%; 83 | height: 100%; 84 | } 85 | 86 | /* Edit Below to Customize Widths > 768px */ 87 | @media (min-width:768px) { 88 | 89 | /* Wrappers */ 90 | 91 | #wrapper { 92 | padding-left: 225px; 93 | } 94 | 95 | #page-wrapper { 96 | padding: 15px 25px; 97 | } 98 | 99 | /* Side Nav */ 100 | 101 | .side-nav { 102 | margin-left: -225px; 103 | left: 225px; 104 | width: 225px; 105 | position: fixed; 106 | top: 50px; 107 | height: 100%; 108 | border-radius: 0; 109 | border: none; 110 | background-color: #222222; 111 | overflow-y: auto; 112 | } 113 | 114 | /* Bootstrap Default Overrides - Customized Dropdowns for the Side Nav */ 115 | 116 | .side-nav>li.dropdown>ul.dropdown-menu { 117 | position: relative; 118 | min-width: 225px; 119 | margin: 0; 120 | padding: 0; 121 | border: none; 122 | border-radius: 0; 123 | background-color: transparent; 124 | box-shadow: none; 125 | -webkit-box-shadow: none; 126 | } 127 | 128 | .side-nav>li.dropdown>ul.dropdown-menu>li>a { 129 | color: #999999; 130 | padding: 15px 15px 15px 25px; 131 | } 132 | 133 | .side-nav>li.dropdown>ul.dropdown-menu>li>a:hover, 134 | .side-nav>li.dropdown>ul.dropdown-menu>li>a.active, 135 | .side-nav>li.dropdown>ul.dropdown-menu>li>a:focus { 136 | color: #fff; 137 | background-color: #080808; 138 | } 139 | 140 | .side-nav>li>a { 141 | width: 225px; 142 | } 143 | 144 | .navbar-inverse .navbar-nav>li>a:hover, 145 | .navbar-inverse .navbar-nav>li>a:focus { 146 | background-color: #080808; 147 | } 148 | 149 | /* Nav Messages */ 150 | 151 | .messages-dropdown .dropdown-menu { 152 | min-width: 300px; 153 | } 154 | 155 | .messages-dropdown .dropdown-menu li a { 156 | white-space: normal; 157 | } 158 | 159 | .navbar-collapse { 160 | padding-left: 15px !important; 161 | padding-right: 15px !important; 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in 3 | # config/application.rb. 4 | 5 | # Host, to be used for routes and Action Mailer. 6 | config.x.host = 'zeus.ugent.be' 7 | config.relative_url_root = '/game' 8 | 9 | # Code is not reloaded between requests. 10 | config.cache_classes = true 11 | 12 | # Eager load code on boot. This eager loads most of Rails and 13 | # your application in memory, allowing both threaded web servers 14 | # and those relying on copy on write to perform better. 15 | # Rake tasks automatically ignore this option for performance. 16 | config.eager_load = true 17 | 18 | # Full error reports are disabled and caching is turned on. 19 | config.consider_all_requests_local = false 20 | config.action_controller.perform_caching = true 21 | 22 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 23 | # Add `rack-cache` to your Gemfile before enabling this. 24 | # For large-scale production use, consider using a caching reverse proxy like 25 | # nginx, varnish or squid. 26 | # config.action_dispatch.rack_cache = true 27 | 28 | # Disable Rails's static asset server (Apache or nginx will already do this). 29 | config.serve_static_assets = false 30 | 31 | # Compress JavaScripts and CSS. 32 | config.assets.js_compressor = :uglifier 33 | # config.assets.css_compressor = :sass 34 | 35 | # Do not fallback to assets pipeline if a precompiled asset is missed. 36 | config.assets.compile = false 37 | 38 | # Generate digests for assets URLs. 39 | config.assets.digest = true 40 | 41 | # `config.assets.precompile` has moved to config/initializers/assets.rb 42 | 43 | # Specifies the header that your server uses for sending files. 44 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 45 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 46 | 47 | # Force all access to the app over SSL, use Strict-Transport-Security, and 48 | # use secure cookies. 49 | # config.force_ssl = true 50 | 51 | # Set to :debug to see everything in the log. 52 | config.log_level = :info 53 | 54 | # Prepend all log lines with the following tags. 55 | # config.log_tags = [ :subdomain, :uuid ] 56 | 57 | # Use a different logger for distributed setups. 58 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 59 | 60 | # Use a different cache store in production. 61 | # config.cache_store = :mem_cache_store 62 | 63 | # Enable serving of images, stylesheets, and JavaScripts from an asset 64 | # server. 65 | # config.action_controller.asset_host = "http://assets.example.com" 66 | 67 | # Precompile additional assets. application.js, application.css, and all 68 | # non-JS/CSS in app/assets folder are already added. 69 | # config.assets.precompile += %w( search.js ) 70 | 71 | # Ignore bad email addresses and do not raise email delivery errors. 72 | # Set this to true and configure the email server for immediate delivery to 73 | # raise delivery errors. 74 | # config.action_mailer.raise_delivery_errors = false 75 | config.action_mailer.default_url_options = { 76 | host: config.x.host, 77 | script_name: config.relative_url_root 78 | } 79 | 80 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 81 | # the I18n.default_locale when a translation cannot be found). 82 | config.i18n.fallbacks = true 83 | 84 | # Send deprecation notices to registered listeners. 85 | config.active_support.deprecation = :notify 86 | 87 | # Disable automatic flushing of the log to improve performance. 88 | # config.autoflush_log = false 89 | 90 | # Use default logging formatter so that PID and timestamp are not suppressed. 91 | config.log_formatter = ::Logger::Formatter.new 92 | 93 | # Do not dump schema after migrations. 94 | config.active_record.dump_schema_after_migration = false 95 | end 96 | 97 | # Remove me when updated to > 4.2 98 | Rails.application.routes.default_url_options[:script_name] = '/game' 99 | -------------------------------------------------------------------------------- /spec/controllers/bounties_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe BountiesController, type: :controller do 2 | # include Devise::TestHelpers 3 | 4 | before :each do 5 | @issue = create :issue 6 | @coder = create :coder, absolute_bounty_residual: 100 7 | sign_in @coder 8 | end 9 | 10 | it 'correctly sets current user' do 11 | expect(subject.current_coder).to eq(@coder) 12 | end 13 | 14 | context 'bounty' do 15 | before :each do 16 | # Place a bounty 17 | put :update_or_create, 18 | bounty: { issue_id: @issue, value: 10 }, 19 | format: :coffee 20 | end 21 | 22 | it 'is placed' do 23 | expect(@issue.bounties.count).to eq(1) 24 | end 25 | 26 | it 'has correct bounty value' do 27 | expect(@issue.total_bounty_value).to eq(10) 28 | end 29 | 30 | it 'was paid for by issuer' do 31 | @coder.reload 32 | expect(@issue.total_bounty_value).to eq(10) 33 | expect(@coder.bounty_residual).to eq(90) 34 | end 35 | 36 | context 'when updated' do 37 | before :each do 38 | put :update_or_create, 39 | bounty: { issue_id: @issue, value: 20 }, 40 | format: :coffee 41 | end 42 | 43 | it 'does not create a new bounty' do 44 | expect(@issue.bounties.count).to eq(1) 45 | end 46 | 47 | it 'updates value' do 48 | expect(@issue.total_bounty_value).to eq(20) 49 | end 50 | end 51 | end 52 | 53 | context 'dumb coder' do 54 | it 'cannot place bounties with insufficient bounty points' do 55 | put :update_or_create, 56 | bounty: { issue_id: @issue, value: 110 }, 57 | format: :coffee 58 | expect(@coder.bounty_residual).to eq(100) 59 | expect(@issue.total_bounty_value).to eq(0) 60 | end 61 | 62 | it 'cannot place bounties with negative value' do 63 | put :update_or_create, 64 | bounty: { issue_id: @issue, value: -10 }, 65 | format: :coffee 66 | expect(@coder.bounty_residual).to eq(100) 67 | expect(@issue.total_bounty_value).to eq(0) 68 | end 69 | 70 | it 'cannot place bounties with non-numeric value' do 71 | put :update_or_create, 72 | bounty: { issue_id: @issue, value: 'A' }, 73 | format: :coffee 74 | expect(@coder.bounty_residual).to eq(100) 75 | expect(@issue.total_bounty_value).to eq(0) 76 | end 77 | end 78 | 79 | context 'claimed issue' do 80 | before :each do 81 | # First claim 82 | @first_claimant = create :coder 83 | @issue.assignee = @first_claimant 84 | put :update_or_create, 85 | bounty: { issue_id: @issue, value: 20 }, 86 | format: :coffee 87 | @issue.close! 88 | end 89 | 90 | it 'has a claimed bounty' do 91 | expect( 92 | Bounty.where(issue: @issue).where.not(claimed_at: nil).count 93 | ).to eq(1) 94 | end 95 | 96 | context 'when reopened' do 97 | before :each do 98 | # Reopen and put a second bounty 99 | @issue.closed_at = nil 100 | put :update_or_create, 101 | bounty: { issue_id: @issue, value: 20 }, 102 | format: :coffee 103 | end 104 | 105 | it 'has a claimed bounty' do 106 | expect(@issue.bounties.where.not(claimed_at: nil).count).to eq(1) 107 | end 108 | 109 | it 'has an unclaimed bounty' do 110 | expect(@issue.bounties.where(claimed_at: nil).count).to eq(1) 111 | end 112 | 113 | context 'and claimed again' do 114 | before :each do 115 | @second_claimant = create :coder 116 | @issue.assignee = @second_claimant 117 | @issue.close! 118 | end 119 | 120 | it 'rewarded first claimant' do 121 | expect(@first_claimant.claimed_value).to eq(20) 122 | end 123 | 124 | it 'rewarded second claimant' do 125 | expect(@second_claimant.claimed_value).to eq(20) 126 | end 127 | end 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n 2 | 3 | en: 4 | devise: 5 | confirmations: 6 | confirmed: "Your account was successfully confirmed." 7 | send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes." 8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions about how to confirm your account in a few minutes." 9 | failure: 10 | already_authenticated: "You are already signed in." 11 | inactive: "Your account is not activated yet." 12 | invalid: "Invalid email or password." 13 | locked: "Your account is locked." 14 | last_attempt: "You have one more attempt before your account will be locked." 15 | not_found_in_database: "Invalid email or password." 16 | timeout: "Your session expired. Please sign in again to continue." 17 | unauthenticated: "You need to sign in or sign up before continuing." 18 | unconfirmed: "You have to confirm your account before continuing." 19 | mailer: 20 | confirmation_instructions: 21 | subject: "Confirmation instructions" 22 | reset_password_instructions: 23 | subject: "Reset password instructions" 24 | unlock_instructions: 25 | subject: "Unlock Instructions" 26 | omniauth_callbacks: 27 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"." 28 | success: "Successfully authenticated from %{kind} account." 29 | passwords: 30 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." 31 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." 32 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." 33 | updated: "Your password was changed successfully. You are now signed in." 34 | updated_not_active: "Your password was changed successfully." 35 | registrations: 36 | destroyed: "Bye! Your account was successfully cancelled. We hope to see you again soon." 37 | signed_up: "Welcome! You have signed up successfully." 38 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." 39 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." 40 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please open the link to activate your account." 41 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and click on the confirm link to finalize confirming your new email address." 42 | updated: "You updated your account successfully." 43 | sessions: 44 | signed_in: "Signed in successfully." 45 | signed_out: "Signed out successfully." 46 | unlocks: 47 | send_instructions: "You will receive an email with instructions about how to unlock your account in a few minutes." 48 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions about how to unlock it in a few minutes." 49 | unlocked: "Your account has been unlocked successfully. Please sign in to continue." 50 | errors: 51 | messages: 52 | already_confirmed: "was already confirmed, please try signing in" 53 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" 54 | expired: "has expired, please request a new one" 55 | not_found: "not found" 56 | not_locked: "was not locked" 57 | not_saved: 58 | one: "1 error prohibited this %{resource} from being saved:" 59 | other: "%{count} errors prohibited this %{resource} from being saved:" 60 | -------------------------------------------------------------------------------- /spec/models/bounty_spec.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: bounties 4 | # 5 | # id :integer not null, primary key 6 | # absolute_value :integer not null 7 | # issue_id :integer not null 8 | # issuer_id :integer not null 9 | # claimant_id :integer 10 | # claimed_value :integer 11 | # claimed_at :datetime 12 | # created_at :datetime 13 | # updated_at :datetime 14 | # 15 | 16 | describe Bounty do 17 | before :each do 18 | @issuer = create :coder 19 | @issue = create :issue 20 | @bounty = create :bounty, issue: @issue, issuer: @issuer, absolute_value: 100 21 | end 22 | 23 | it 'has a valid factory' do 24 | expect(@bounty).to be_valid 25 | end 26 | 27 | it 'refunds bounty points when issue was not claimed' do 28 | @bounty.claim! 29 | expect(@issuer.bounty_residual).to eq(100) 30 | end 31 | 32 | context 'claimed by issuer' do 33 | before :each do 34 | @issue.assignee = @issuer 35 | @bounty.claim! 36 | end 37 | 38 | it 'refunds bounty points' do 39 | expect(@issuer.bounty_residual).to eq(100) 40 | end 41 | 42 | it 'deletes itself' do 43 | expect(@bounty.destroyed?).to be_truthy 44 | end 45 | end 46 | 47 | context 'when claimed' do 48 | before :each do 49 | @claimant = create :coder 50 | @issue.assignee = @claimant 51 | @bounty.claim! 52 | end 53 | 54 | it 'sets claimed time' do 55 | expect(@bounty.claimed_at).to be 56 | end 57 | 58 | it 'sets claimed value' do 59 | expect(@bounty.claimed_value).to eq(100) 60 | end 61 | 62 | it 'sets claimant' do 63 | expect(@bounty.claimant).to eq(@claimant) 64 | end 65 | 66 | it 'cannot be claimed again' do 67 | contender = create :coder 68 | @issue.assignee = contender 69 | @bounty.claim! 70 | expect(@bounty.claimant).to eq(@claimant) 71 | end 72 | 73 | it 'clears its bounty value' do 74 | expect(@bounty.absolute_value).to eq(0) 75 | end 76 | end 77 | 78 | context 'with scaled value' do 79 | before :each do 80 | @limit = Rails.application.config.total_bounty_value 81 | @bounty.update!(absolute_value: @limit * 2) 82 | @assignee = create :coder 83 | @bounty.issue.assignee = @assignee 84 | end 85 | 86 | it 'has a scaled value' do 87 | expect(@bounty.absolute_value).to eq(2 * @limit) 88 | expect(@bounty.value).to eq(@limit) 89 | end 90 | 91 | it 'rewards a scaled value' do 92 | @bounty.claim! 93 | expect(@assignee.reward_residual).to eq(@limit) 94 | end 95 | 96 | it 'rewards a scaled amount of bounty points' do 97 | @bounty.claim! 98 | expect(@assignee.absolute_bounty_residual).to eq(2 * @limit) 99 | expect(@assignee.bounty_residual).to eq(@limit) 100 | end 101 | end 102 | 103 | context 'with other bounty points in the running' do 104 | before :each do 105 | @limit = Rails.application.config.total_bounty_value 106 | @issuer.update!(absolute_bounty_residual: @limit - 100) 107 | @other_coder = create :coder, absolute_bounty_residual: 2 * @limit 108 | end 109 | 110 | it 'has a rescaled value' do 111 | expect(@bounty.absolute_value).to eq(100) 112 | expect(@bounty.value).to eq(33) 113 | end 114 | 115 | context 'when claimed' do 116 | before :each do 117 | @issue.assignee = @other_coder 118 | @bounty.claim! 119 | end 120 | 121 | it 'has a claimed value, but no absolute_value' do 122 | expect(@bounty.absolute_value).to eq(0) 123 | expect(@bounty.claimed_value).to eq(33) 124 | expect(@bounty.value).to eq(33) 125 | end 126 | 127 | context 'and when other bounties come to exist' do 128 | before :each do 129 | @other_bounty = create :bounty, absolute_value: 1000 130 | end 131 | 132 | it 'has a claimed value, but no absolute_value' do 133 | expect(@bounty.absolute_value).to eq(0) 134 | expect(@bounty.claimed_value).to eq(33) 135 | expect(@bounty.value).to eq(33) 136 | end 137 | end 138 | end 139 | end 140 | end 141 | -------------------------------------------------------------------------------- /app/assets/javascripts/top4.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | 5 | 6 | $('body.top4.show').ready -> 7 | # `repos` and `contributors` are defined before this gets executed 8 | 9 | for [repo, repo_contributors] in d3.zip(repos, contributors) 10 | do ([repo, repo_contributors]) -> 11 | # Only select the top 3 contributors and put the rest in and 'other' user 12 | repo.contributors = repo_contributors[0..2] 13 | if repo_contributors.length > 3 14 | repo.contributors += 15 | github_name: 'other' 16 | score: d3.sum(repo_contributors[3..], (d) -> d.score) 17 | 18 | # Calculate offset for contributor bars 19 | sum = 0 20 | for contributor in repo.contributors by -1 21 | do (contributor) -> 22 | contributor.y0 = sum 23 | sum += contributor.score 24 | 25 | margin = {top: 20, right: 0, bottom: 30, left: 0} 26 | width = 600 - margin.right - margin.left 27 | height = 300 - margin.top - margin.bottom 28 | 29 | x = d3.scale.ordinal() 30 | .rangeRoundBands([0, width], .1) 31 | .domain(repos.map((d) -> d.name)) 32 | 33 | y = d3.scale.linear() 34 | .range([0, height]) 35 | .domain([0, d3.max(repos, (d) -> d.score)]) 36 | 37 | colors = d3.scale.ordinal() 38 | .range(['#1976d2','#2196f3', '#64b5f6','#bbdefb']) 39 | .domain([0,1,2,3]) 40 | 41 | chart = d3.select('#top-repos') 42 | .append('g') 43 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') 44 | 45 | repo_groups = chart.selectAll('g') 46 | .data(repos) 47 | .enter().append('g') 48 | .attr 'transform', (d) -> 49 | 'translate(' + x(d.name) + ',' + (height - y(d.score)) + ')' 50 | 51 | repo_groups.append('rect') 52 | .attr('class', 'repo-bar') 53 | .attr('y', (d) -> y(d.score)) 54 | .attr('height', 0) 55 | .attr('width', x.rangeBand()) 56 | .transition().duration(1000) 57 | .attr('y', 0) 58 | .attr('height', (d) -> y(d.score)) 59 | 60 | repo_groups.append('text') 61 | .attr('class', 'repo-label') 62 | .attr('x', x.rangeBand() / 2) 63 | .attr('y', '.5em') 64 | .attr('opacity', 0) 65 | .text((d) -> d.score) 66 | 67 | contributor_groups = repo_groups.selectAll('.contributor') 68 | .data((d) -> d.contributors) 69 | .enter().append('g') 70 | .attr('class', 'contributor') 71 | .attr('transform', 'translate(0,' + height + ')') 72 | 73 | # Collapse groups 74 | repo_groups.each (d) -> 75 | d3.select(this) 76 | .selectAll('.contributor') 77 | .attr('transform', 'translate(0,' + y(d.score) + ')') 78 | 79 | contributor_groups 80 | .append('rect') 81 | .attr('class', 'contributor-bar') 82 | .attr('height', 0) 83 | .attr('width', x.rangeBand()) 84 | .style('fill', (d, i) -> colors(i)) 85 | 86 | contributor_groups 87 | .filter((d) -> y(d.score) > 15) 88 | .append('text') 89 | .attr('class', 'contributor-name') 90 | .attr('x', x.rangeBand() / 2) 91 | .attr('y', '1em') 92 | .text((d) -> d.github_name) 93 | 94 | repo_groups 95 | .on 'mouseenter', (d) -> 96 | t = d3.select(this).transition().duration(500) 97 | # Breakdown 98 | t.selectAll('.contributor') 99 | .attr('transform', (d) -> 'translate(0,' + y(d.y0) + ')') 100 | t.selectAll('.contributor-bar') 101 | .attr('height', (d) -> y(d.score)) 102 | # Label 103 | t.select('.repo-label') 104 | .attr('y', '-.25em') 105 | .attr('opacity', 1) 106 | .on 'mouseleave', (d) -> 107 | t = d3.select(this).transition().duration(500) 108 | # Breakdown 109 | t.selectAll('.contributor') 110 | .attr('transform', 'translate(0,' + y(d.score) + ')') 111 | t.selectAll('.contributor-bar') 112 | .attr('height', 0) 113 | # Label 114 | t.select('.repo-label') 115 | .attr('y', '.5em') 116 | .attr('opacity', 0) 117 | 118 | xAxis = d3.svg.axis() 119 | .scale(x) 120 | .orient('bottom') 121 | 122 | repo_name_nodes = chart.append('g') 123 | .attr('class', 'axis xaxis') 124 | .attr('transform', 'translate(0,' + height + ')') 125 | .call(xAxis) 126 | .selectAll('text') 127 | .attr('dy', '1em') 128 | 129 | # Add links to repo names in x axis 130 | d3.zip(repo_name_nodes[0], repos).forEach (tuple) -> 131 | text_node = $(tuple[0]) 132 | repo = tuple[1] 133 | text_node 134 | .css('cursor', 'pointer') 135 | .hover( 136 | -> $(this).css('text-decoration', 'underline') 137 | -> $(this).css('text-decoration', 'inherit') 138 | ) 139 | .click (event) -> 140 | if event.ctrlKey 141 | window.open(repo.base_uri, '_blank') 142 | else 143 | document.location.href = repo.base_uri 144 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20150829193440) do 15 | 16 | create_table "bounties", force: true do |t| 17 | t.integer "absolute_value", null: false 18 | t.integer "issue_id", null: false 19 | t.integer "issuer_id", null: false 20 | t.integer "claimant_id" 21 | t.integer "claimed_value" 22 | t.datetime "claimed_at" 23 | t.datetime "created_at" 24 | t.datetime "updated_at" 25 | end 26 | 27 | add_index "bounties", ["claimant_id"], name: "index_bounties_on_claimant_id", using: :btree 28 | add_index "bounties", ["issue_id", "issuer_id"], name: "index_bounties_on_issue_id_and_issuer_id", using: :btree 29 | add_index "bounties", ["issue_id"], name: "index_bounties_on_issue_id", using: :btree 30 | add_index "bounties", ["issuer_id"], name: "index_bounties_on_issuer_id", using: :btree 31 | 32 | create_table "coders", force: true do |t| 33 | t.string "github_name", null: false 34 | t.string "full_name", default: "", null: false 35 | t.string "avatar_url", null: false 36 | t.string "github_url", null: false 37 | t.integer "reward_residual", default: 0, null: false 38 | t.integer "absolute_bounty_residual", default: 0, null: false 39 | t.integer "other_score", default: 0, null: false 40 | t.datetime "created_at" 41 | t.datetime "updated_at" 42 | end 43 | 44 | add_index "coders", ["github_name"], name: "index_coders_on_github_name", unique: true, using: :btree 45 | add_index "coders", ["github_url"], name: "index_coders_on_github_url", unique: true, using: :btree 46 | 47 | create_table "commits", force: true do |t| 48 | t.integer "coder_id" 49 | t.integer "repository_id" 50 | t.string "sha", null: false 51 | t.integer "additions", default: 0, null: false 52 | t.integer "deletions", default: 0, null: false 53 | t.datetime "date", null: false 54 | t.datetime "created_at" 55 | t.datetime "updated_at" 56 | end 57 | 58 | add_index "commits", ["coder_id"], name: "index_commits_on_coder_id", using: :btree 59 | add_index "commits", ["repository_id", "sha"], name: "index_commits_on_repository_id_and_sha", unique: true, using: :btree 60 | add_index "commits", ["repository_id"], name: "index_commits_on_repository_id", using: :btree 61 | 62 | create_table "git_identities", force: true do |t| 63 | t.string "name", null: false 64 | t.string "email", null: false 65 | t.integer "coder_id" 66 | t.datetime "created_at" 67 | t.datetime "updated_at" 68 | end 69 | 70 | add_index "git_identities", ["coder_id"], name: "index_git_identities_on_coder_id", using: :btree 71 | add_index "git_identities", ["name", "email"], name: "index_git_identities_on_name_and_email", unique: true, using: :btree 72 | 73 | create_table "issues", force: true do |t| 74 | t.string "github_url", null: false 75 | t.integer "number", null: false 76 | t.string "title", default: "Untitled", null: false 77 | t.integer "issuer_id", null: false 78 | t.integer "repository_id", null: false 79 | t.text "labels", null: false 80 | t.text "body" 81 | t.integer "assignee_id" 82 | t.string "milestone" 83 | t.datetime "opened_at", null: false 84 | t.datetime "closed_at" 85 | t.datetime "created_at" 86 | t.datetime "updated_at" 87 | end 88 | 89 | add_index "issues", ["github_url"], name: "index_issues_on_github_url", unique: true, using: :btree 90 | add_index "issues", ["repository_id", "number"], name: "index_issues_on_repository_id_and_number", unique: true, using: :btree 91 | add_index "issues", ["repository_id"], name: "index_issues_on_repository_id", using: :btree 92 | 93 | create_table "repositories", force: true do |t| 94 | t.string "name", null: false 95 | t.string "github_url", null: false 96 | t.string "clone_url", null: false 97 | t.datetime "created_at" 98 | t.datetime "updated_at" 99 | end 100 | 101 | end 102 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'coveralls' 2 | Coveralls.wear! 3 | 4 | # This file was generated by the `rails generate rspec:install` command. 5 | # Conventionally, all specs live under a `spec` directory, which RSpec adds to 6 | # the `$LOAD_PATH`. The generated `.rspec` file contains `--require 7 | # spec_helper` which will cause this file to always be loaded, without a need 8 | # to explicitly require it in any files. 9 | # 10 | # Given that it is always loaded, you are encouraged to keep this file as 11 | # light-weight as possible. Requiring heavyweight dependencies from this file 12 | # will add to the boot time of your test suite on EVERY test run, even for an 13 | # individual file that may not need all of that loaded. Instead, consider 14 | # making a separate helper file that requires the additional dependencies and 15 | # performs the additional setup, and require it from the spec files that 16 | # actually need it. 17 | # 18 | # The `.rspec` file also contains a few flags that are not defaults but that 19 | # users commonly want. 20 | # 21 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 22 | 23 | require 'factory_girl' 24 | require 'devise' 25 | # Requrire all files in spec/support/ 26 | Dir[File.join(File.dirname(__FILE__), 'support', '**', '*.rb')].each do |f| 27 | require f 28 | end 29 | 30 | RSpec.configure do |config| 31 | # use FactoryGirl 32 | config.include FactoryGirl::Syntax::Methods 33 | config.include Devise::TestHelpers, type: :controller 34 | # rspec-expectations config goes here. You can use an alternate 35 | # assertion/expectation library such as wrong or the stdlib/minitest 36 | # assertions if you prefer. 37 | config.expect_with :rspec do |expectations| 38 | # This option will default to `true` in RSpec 4. It makes the `description` 39 | # and `failure_message` of custom matchers include text for helper methods 40 | # defined using `chain`, e.g.: 41 | # be_bigger_than(2).and_smaller_than(4).description 42 | # # => "be bigger than 2 and smaller than 4" 43 | # ...rather than: 44 | # # => "be bigger than 2" 45 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 46 | end 47 | 48 | # rspec-mocks config goes here. You can use an alternate test double 49 | # library (such as bogus or mocha) by changing the `mock_with` option here. 50 | config.mock_with :rspec do |mocks| 51 | # Prevents you from mocking or stubbing a method that does not exist on 52 | # a real object. This is generally recommended, and will default to 53 | # `true` in RSpec 4. 54 | mocks.verify_partial_doubles = true 55 | end 56 | 57 | # Let RSpec remember failures, so we can run '--only-failures' next time. 58 | config.example_status_persistence_file_path = './spec/.failures.txt' 59 | 60 | # The settings below are suggested to provide a good initial experience 61 | # with RSpec, but feel free to customize to your heart's content. 62 | # # These two settings work together to allow you to limit a spec run 63 | # # to individual examples or groups you care about by tagging them with 64 | # # `:focus` metadata. When nothing is tagged with `:focus`, all examples 65 | # # get run. 66 | # config.filter_run :focus 67 | # config.run_all_when_everything_filtered = true 68 | # 69 | # # Limits the available syntax to the non-monkey patched syntax that is 70 | # # recommended. 71 | # # For more details, see: 72 | # # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 73 | # # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 74 | # # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching 75 | # config.disable_monkey_patching! 76 | # 77 | # # Many RSpec users commonly either run the entire suite or an individual 78 | # # file, and it's useful to allow more verbose output when running an 79 | # # individual spec file. 80 | # if config.files_to_run.one? 81 | # # Use the documentation formatter for detailed output, 82 | # # unless a formatter has already been configured 83 | # # (e.g. via a command-line flag). 84 | # config.default_formatter = 'doc' 85 | # end 86 | # 87 | # # Print the 10 slowest examples and example groups at the 88 | # # end of the spec run, to help surface which specs are running 89 | # # particularly slow. 90 | # config.profile_examples = 10 91 | # 92 | # # Run specs in random order to surface order dependencies. If you find an 93 | # # order dependency and want to debug it, you can fix the order by 94 | # # providing the seed, which is printed after each run. 95 | # # --seed 1234 96 | # config.order = :random 97 | # 98 | # # Seed global randomization in this process using the `--seed` CLI 99 | # # option. Setting this allows you to use `--seed` to deterministically 100 | # # reproduce test failures related to randomization by passing the same 101 | # # `--seed` value as the one that triggered the failure. 102 | # Kernel.srand config.seed 103 | end 104 | -------------------------------------------------------------------------------- /spec/models/coder_spec.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: coders 4 | # 5 | # id :integer not null, primary key 6 | # github_name :string(255) not null 7 | # full_name :string(255) default(""), not null 8 | # avatar_url :string(255) not null 9 | # github_url :string(255) not null 10 | # reward_residual :integer default(0), not null 11 | # absolute_bounty_residual :integer default(0), not null 12 | # other_score :integer default(0), not null 13 | # created_at :datetime 14 | # updated_at :datetime 15 | # 16 | 17 | describe Coder do 18 | before :each do 19 | @coder = create :coder 20 | end 21 | 22 | it 'has a valid factory' do 23 | expect(@coder).to be_valid 24 | end 25 | 26 | context 'with commits' do 27 | before :each do 28 | create :commit, coder: @coder, additions: 10, deletions: 4 29 | create :commit, coder: @coder, additions: 20, deletions: 8 30 | create :commit, coder: @coder, additions: 12, deletions: 7 31 | 32 | @addition_score = [10, 20, 12].map do |n| 33 | Rails.application.config.addition_score_factor * 34 | Math.log(n + 1) 35 | end.map(&:round).sum 36 | 37 | @coder.commits.each do |commit| 38 | @coder.reward_commit! commit 39 | end 40 | end 41 | 42 | it 'has a correct addition count' do 43 | expect(@coder.additions).to eq(42) 44 | end 45 | 46 | it 'has a correct deletion count' do 47 | expect(@coder.deletions).to eq(19) 48 | end 49 | 50 | it 'was granted reward points' do 51 | expect(@coder.reward_residual).to eq(@addition_score) 52 | end 53 | 54 | it 'was granted bounty points' do 55 | expect(@coder.bounty_residual).to eq(@addition_score) 56 | end 57 | 58 | it 'has a correct score' do 59 | expect(@coder.score).to eq(@addition_score) 60 | end 61 | end 62 | 63 | context 'with claimed bounty' do 64 | before :each do 65 | @issue = create :issue, assignee: @coder 66 | @bounty = create :bounty, issue: @issue, absolute_value: 100 67 | @bounty.claim! 68 | end 69 | 70 | it 'has a corrent claimed_value stat' do 71 | expect(@coder.claimed_value).to eq(100) 72 | end 73 | 74 | it 'has a correct score' do 75 | expect(@coder.score).to eq(100) 76 | end 77 | 78 | it 'was granted reward points' do 79 | expect(@coder.reward_residual).to eq(100) 80 | end 81 | 82 | it 'was granted bounty points' do 83 | expect(@coder.bounty_residual).to eq(100) 84 | end 85 | end 86 | 87 | context 'with other bounty points in the running' do 88 | before :each do 89 | @limit = Rails.application.config.total_bounty_value 90 | @coder.update!(absolute_bounty_residual: @limit) 91 | @other_coder = create :coder, absolute_bounty_residual: 2 * @limit 92 | end 93 | 94 | it 'ensures a correct total amount of bounty points' do 95 | expect(BountyPoints.total_bounty_points).to almost_eq(3 * @limit) 96 | end 97 | 98 | it 'has a rescaled bounty residual' do 99 | expect(@coder.absolute_bounty_residual).to eq(@limit) 100 | expect(@other_coder.absolute_bounty_residual).to eq(2 * @limit) 101 | expect(@coder.bounty_residual).to eq((@limit.to_f / 3).round) 102 | expect(@other_coder.bounty_residual).to eq((2 * @limit.to_f / 3).round) 103 | end 104 | 105 | context 'with a bounty placed' do 106 | before :each do 107 | @issue = create :issue 108 | @bounty = create :bounty, issue: @issue, 109 | absolute_value: 0, 110 | issuer: @coder 111 | @bounty.update_value!(100) 112 | end 113 | 114 | it 'ensures total amount of bounty points stays the same' do 115 | expect(BountyPoints.total_bounty_points).to almost_eq(3 * @limit) 116 | end 117 | 118 | it 'has a rescaled bounty residual' do 119 | expect(@coder.bounty_residual).to eq((@limit.to_f / 3).round - 100) 120 | expect(@coder.absolute_bounty_residual).to almost_eq(((@limit.to_f / 3).round - 100) * 3) 121 | end 122 | 123 | context 'and self-claimed' do 124 | before :each do 125 | @issue.assignee = @coder 126 | @bounty.claim! 127 | end 128 | 129 | it 'ensures total amount of bounty points stays the same' do 130 | expect(BountyPoints.total_bounty_points).to almost_eq(3 * @limit) 131 | end 132 | 133 | it 'has his bounty points refunded' do 134 | expect(@coder.absolute_bounty_residual).to almost_eq(@limit) 135 | expect(@coder.bounty_residual).to eq((@limit.to_f / 3).round) 136 | end 137 | end 138 | 139 | context 'and claimed by someone else' do 140 | before :each do 141 | @issue.assignee = @other_coder 142 | @bounty.claim! 143 | end 144 | 145 | it 'has attributed reward and bounty points to the other coder' do 146 | expect(@other_coder.bounty_residual).to eq((2 * @limit.to_f / 3).round + 100) 147 | expect(@other_coder.absolute_bounty_residual).to almost_eq(((2 * @limit.to_f / 3).round + 100) * 3) 148 | expect(@other_coder.reward_residual).to eq(100) 149 | end 150 | end 151 | end 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /spec/github_jsons/repo_create.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "created", 3 | "repository": { 4 | "id": 33015564, 5 | "name": "glowing-octo-dubstep", 6 | "full_name": "ZeusWPI/glowing-octo-dubstep", 7 | "owner": { 8 | "login": "ZeusWPI", 9 | "id": 331750, 10 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3", 11 | "gravatar_id": "", 12 | "url": "https://api.github.com/users/ZeusWPI", 13 | "html_url": "https://github.com/ZeusWPI", 14 | "followers_url": "https://api.github.com/users/ZeusWPI/followers", 15 | "following_url": "https://api.github.com/users/ZeusWPI/following{/other_user}", 16 | "gists_url": "https://api.github.com/users/ZeusWPI/gists{/gist_id}", 17 | "starred_url": "https://api.github.com/users/ZeusWPI/starred{/owner}{/repo}", 18 | "subscriptions_url": "https://api.github.com/users/ZeusWPI/subscriptions", 19 | "organizations_url": "https://api.github.com/users/ZeusWPI/orgs", 20 | "repos_url": "https://api.github.com/users/ZeusWPI/repos", 21 | "events_url": "https://api.github.com/users/ZeusWPI/events{/privacy}", 22 | "received_events_url": "https://api.github.com/users/ZeusWPI/received_events", 23 | "type": "Organization", 24 | "site_admin": false 25 | }, 26 | "private": true, 27 | "html_url": "https://github.com/ZeusWPI/glowing-octo-dubstep", 28 | "description": "", 29 | "fork": false, 30 | "url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep", 31 | "forks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/forks", 32 | "keys_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/keys{/key_id}", 33 | "collaborators_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/collaborators{/collaborator}", 34 | "teams_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/teams", 35 | "hooks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/hooks", 36 | "issue_events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/events{/number}", 37 | "events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/events", 38 | "assignees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/assignees{/user}", 39 | "branches_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/branches{/branch}", 40 | "tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/tags", 41 | "blobs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/blobs{/sha}", 42 | "git_tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/tags{/sha}", 43 | "git_refs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/refs{/sha}", 44 | "trees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/trees{/sha}", 45 | "statuses_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/statuses/{sha}", 46 | "languages_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/languages", 47 | "stargazers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/stargazers", 48 | "contributors_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contributors", 49 | "subscribers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscribers", 50 | "subscription_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscription", 51 | "commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/commits{/sha}", 52 | "git_commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/commits{/sha}", 53 | "comments_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/comments{/number}", 54 | "issue_comment_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/comments{/number}", 55 | "contents_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contents/{+path}", 56 | "compare_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/compare/{base}...{head}", 57 | "merges_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/merges", 58 | "archive_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/{archive_format}{/ref}", 59 | "downloads_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/downloads", 60 | "issues_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues{/number}", 61 | "pulls_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/pulls{/number}", 62 | "milestones_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/milestones{/number}", 63 | "notifications_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/notifications{?since,all,participating}", 64 | "labels_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/labels{/name}", 65 | "releases_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/releases{/id}", 66 | "created_at": "2015-03-28T00:35:00Z", 67 | "updated_at": "2015-03-28T00:35:00Z", 68 | "pushed_at": "2015-03-28T00:35:00Z", 69 | "git_url": "git://github.com/ZeusWPI/glowing-octo-dubstep.git", 70 | "ssh_url": "git@github.com:ZeusWPI/glowing-octo-dubstep.git", 71 | "clone_url": "https://github.com/ZeusWPI/glowing-octo-dubstep.git", 72 | "svn_url": "https://github.com/ZeusWPI/glowing-octo-dubstep", 73 | "homepage": null, 74 | "size": 0, 75 | "stargazers_count": 0, 76 | "watchers_count": 0, 77 | "language": null, 78 | "has_issues": true, 79 | "has_downloads": true, 80 | "has_wiki": true, 81 | "has_pages": false, 82 | "forks_count": 0, 83 | "mirror_url": null, 84 | "open_issues_count": 0, 85 | "forks": 0, 86 | "open_issues": 0, 87 | "watchers": 0, 88 | "default_branch": "master" 89 | }, 90 | "organization": { 91 | "login": "ZeusWPI", 92 | "id": 331750, 93 | "url": "https://api.github.com/orgs/ZeusWPI", 94 | "repos_url": "https://api.github.com/orgs/ZeusWPI/repos", 95 | "events_url": "https://api.github.com/orgs/ZeusWPI/events", 96 | "members_url": "https://api.github.com/orgs/ZeusWPI/members{/member}", 97 | "public_members_url": "https://api.github.com/orgs/ZeusWPI/public_members{/member}", 98 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3", 99 | "description": null 100 | }, 101 | "sender": { 102 | "login": "Iasoon", 103 | "id": 6320308, 104 | "avatar_url": "https://avatars.githubusercontent.com/u/6320308?v=3", 105 | "gravatar_id": "", 106 | "url": "https://api.github.com/users/Iasoon", 107 | "html_url": "https://github.com/Iasoon", 108 | "followers_url": "https://api.github.com/users/Iasoon/followers", 109 | "following_url": "https://api.github.com/users/Iasoon/following{/other_user}", 110 | "gists_url": "https://api.github.com/users/Iasoon/gists{/gist_id}", 111 | "starred_url": "https://api.github.com/users/Iasoon/starred{/owner}{/repo}", 112 | "subscriptions_url": "https://api.github.com/users/Iasoon/subscriptions", 113 | "organizations_url": "https://api.github.com/users/Iasoon/orgs", 114 | "repos_url": "https://api.github.com/users/Iasoon/repos", 115 | "events_url": "https://api.github.com/users/Iasoon/events{/privacy}", 116 | "received_events_url": "https://api.github.com/users/Iasoon/received_events", 117 | "type": "User", 118 | "site_admin": false 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /spec/github_jsons/push.json: -------------------------------------------------------------------------------- 1 | { 2 | "ref": "refs/heads/master", 3 | "before": "afbd137b4ebab456e4e0add4b158c6e6ed0adce6", 4 | "after": "be981ec9e53ef639361ffebbf88845c711fb07bd", 5 | "created": false, 6 | "deleted": false, 7 | "forced": false, 8 | "base_ref": null, 9 | "compare": "https://github.com/ZeusWPI/glowing-octo-dubstep/compare/afbd137b4eba...be981ec9e53e", 10 | "commits": [ 11 | { 12 | "id": "be981ec9e53ef639361ffebbf88845c711fb07bd", 13 | "distinct": true, 14 | "message": "Belangrijke informatie toegevoegd", 15 | "timestamp": "2015-04-04T02:37:06+02:00", 16 | "url": "https://github.com/ZeusWPI/glowing-octo-dubstep/commit/be981ec9e53ef639361ffebbf88845c711fb07bd", 17 | "author": { 18 | "name": "Ilion Beyst", 19 | "email": "ilion.beyst@gmail.com", 20 | "username": "Iasoon" 21 | }, 22 | "committer": { 23 | "name": "Ilion Beyst", 24 | "email": "ilion.beyst@gmail.com", 25 | "username": "Iasoon" 26 | }, 27 | "added": [ 28 | 29 | ], 30 | "removed": [ 31 | 32 | ], 33 | "modified": [ 34 | "README.md" 35 | ] 36 | } 37 | ], 38 | "head_commit": { 39 | "id": "be981ec9e53ef639361ffebbf88845c711fb07bd", 40 | "distinct": true, 41 | "message": "Belangrijke informatie toegevoegd", 42 | "timestamp": "2015-04-04T02:37:06+02:00", 43 | "url": "https://github.com/ZeusWPI/glowing-octo-dubstep/commit/be981ec9e53ef639361ffebbf88845c711fb07bd", 44 | "author": { 45 | "name": "Ilion Beyst", 46 | "email": "ilion.beyst@gmail.com", 47 | "username": "Iasoon" 48 | }, 49 | "committer": { 50 | "name": "Ilion Beyst", 51 | "email": "ilion.beyst@gmail.com", 52 | "username": "Iasoon" 53 | }, 54 | "added": [ 55 | 56 | ], 57 | "removed": [ 58 | 59 | ], 60 | "modified": [ 61 | "README.md" 62 | ] 63 | }, 64 | "repository": { 65 | "id": 33015564, 66 | "name": "glowing-octo-dubstep", 67 | "full_name": "ZeusWPI/glowing-octo-dubstep", 68 | "owner": { 69 | "name": "ZeusWPI", 70 | "email": null 71 | }, 72 | "private": true, 73 | "html_url": "https://github.com/ZeusWPI/glowing-octo-dubstep", 74 | "description": "Top8", 75 | "fork": false, 76 | "url": "https://github.com/ZeusWPI/glowing-octo-dubstep", 77 | "forks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/forks", 78 | "keys_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/keys{/key_id}", 79 | "collaborators_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/collaborators{/collaborator}", 80 | "teams_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/teams", 81 | "hooks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/hooks", 82 | "issue_events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/events{/number}", 83 | "events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/events", 84 | "assignees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/assignees{/user}", 85 | "branches_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/branches{/branch}", 86 | "tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/tags", 87 | "blobs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/blobs{/sha}", 88 | "git_tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/tags{/sha}", 89 | "git_refs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/refs{/sha}", 90 | "trees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/trees{/sha}", 91 | "statuses_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/statuses/{sha}", 92 | "languages_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/languages", 93 | "stargazers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/stargazers", 94 | "contributors_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contributors", 95 | "subscribers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscribers", 96 | "subscription_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscription", 97 | "commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/commits{/sha}", 98 | "git_commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/commits{/sha}", 99 | "comments_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/comments{/number}", 100 | "issue_comment_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/comments{/number}", 101 | "contents_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contents/{+path}", 102 | "compare_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/compare/{base}...{head}", 103 | "merges_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/merges", 104 | "archive_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/{archive_format}{/ref}", 105 | "downloads_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/downloads", 106 | "issues_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues{/number}", 107 | "pulls_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/pulls{/number}", 108 | "milestones_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/milestones{/number}", 109 | "notifications_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/notifications{?since,all,participating}", 110 | "labels_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/labels{/name}", 111 | "releases_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/releases{/id}", 112 | "created_at": 1427502900, 113 | "updated_at": "2015-03-28T02:25:20Z", 114 | "pushed_at": 1428107829, 115 | "git_url": "git://github.com/ZeusWPI/glowing-octo-dubstep.git", 116 | "ssh_url": "git@github.com:ZeusWPI/glowing-octo-dubstep.git", 117 | "clone_url": "https://github.com/ZeusWPI/glowing-octo-dubstep.git", 118 | "svn_url": "https://github.com/ZeusWPI/glowing-octo-dubstep", 119 | "homepage": "", 120 | "size": 0, 121 | "stargazers_count": 0, 122 | "watchers_count": 0, 123 | "language": null, 124 | "has_issues": true, 125 | "has_downloads": true, 126 | "has_wiki": true, 127 | "has_pages": false, 128 | "forks_count": 0, 129 | "mirror_url": null, 130 | "open_issues_count": 0, 131 | "forks": 0, 132 | "open_issues": 0, 133 | "watchers": 0, 134 | "default_branch": "master", 135 | "stargazers": 0, 136 | "master_branch": "master", 137 | "organization": "ZeusWPI" 138 | }, 139 | "pusher": { 140 | "name": "Iasoon", 141 | "email": "ilion.beyst@gmail.com" 142 | }, 143 | "organization": { 144 | "login": "ZeusWPI", 145 | "id": 331750, 146 | "url": "https://api.github.com/orgs/ZeusWPI", 147 | "repos_url": "https://api.github.com/orgs/ZeusWPI/repos", 148 | "events_url": "https://api.github.com/orgs/ZeusWPI/events", 149 | "members_url": "https://api.github.com/orgs/ZeusWPI/members{/member}", 150 | "public_members_url": "https://api.github.com/orgs/ZeusWPI/public_members{/member}", 151 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3", 152 | "description": null 153 | }, 154 | "sender": { 155 | "login": "Iasoon", 156 | "id": 6320308, 157 | "avatar_url": "https://avatars.githubusercontent.com/u/6320308?v=3", 158 | "gravatar_id": "", 159 | "url": "https://api.github.com/users/Iasoon", 160 | "html_url": "https://github.com/Iasoon", 161 | "followers_url": "https://api.github.com/users/Iasoon/followers", 162 | "following_url": "https://api.github.com/users/Iasoon/following{/other_user}", 163 | "gists_url": "https://api.github.com/users/Iasoon/gists{/gist_id}", 164 | "starred_url": "https://api.github.com/users/Iasoon/starred{/owner}{/repo}", 165 | "subscriptions_url": "https://api.github.com/users/Iasoon/subscriptions", 166 | "organizations_url": "https://api.github.com/users/Iasoon/orgs", 167 | "repos_url": "https://api.github.com/users/Iasoon/repos", 168 | "events_url": "https://api.github.com/users/Iasoon/events{/privacy}", 169 | "received_events_url": "https://api.github.com/users/Iasoon/received_events", 170 | "type": "User", 171 | "site_admin": false 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /spec/github_jsons/issue_open.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "opened", 3 | "issue": { 4 | "url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1", 5 | "labels_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1/labels{/name}", 6 | "comments_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1/comments", 7 | "events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1/events", 8 | "html_url": "https://github.com/ZeusWPI/glowing-octo-dubstep/issues/1", 9 | "id": 64936749, 10 | "number": 1, 11 | "title": "Problemen!", 12 | "user": { 13 | "login": "Iasoon", 14 | "id": 6320308, 15 | "avatar_url": "https://avatars.githubusercontent.com/u/6320308?v=3", 16 | "gravatar_id": "", 17 | "url": "https://api.github.com/users/Iasoon", 18 | "html_url": "https://github.com/Iasoon", 19 | "followers_url": "https://api.github.com/users/Iasoon/followers", 20 | "following_url": "https://api.github.com/users/Iasoon/following{/other_user}", 21 | "gists_url": "https://api.github.com/users/Iasoon/gists{/gist_id}", 22 | "starred_url": "https://api.github.com/users/Iasoon/starred{/owner}{/repo}", 23 | "subscriptions_url": "https://api.github.com/users/Iasoon/subscriptions", 24 | "organizations_url": "https://api.github.com/users/Iasoon/orgs", 25 | "repos_url": "https://api.github.com/users/Iasoon/repos", 26 | "events_url": "https://api.github.com/users/Iasoon/events{/privacy}", 27 | "received_events_url": "https://api.github.com/users/Iasoon/received_events", 28 | "type": "User", 29 | "site_admin": false 30 | }, 31 | "labels": [ 32 | 33 | ], 34 | "state": "open", 35 | "locked": false, 36 | "assignee": null, 37 | "milestone": null, 38 | "comments": 0, 39 | "created_at": "2015-03-28T12:28:07Z", 40 | "updated_at": "2015-03-28T12:28:07Z", 41 | "closed_at": null, 42 | "body": "" 43 | }, 44 | "repository": { 45 | "id": 33015564, 46 | "name": "glowing-octo-dubstep", 47 | "full_name": "ZeusWPI/glowing-octo-dubstep", 48 | "owner": { 49 | "login": "ZeusWPI", 50 | "id": 331750, 51 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3", 52 | "gravatar_id": "", 53 | "url": "https://api.github.com/users/ZeusWPI", 54 | "html_url": "https://github.com/ZeusWPI", 55 | "followers_url": "https://api.github.com/users/ZeusWPI/followers", 56 | "following_url": "https://api.github.com/users/ZeusWPI/following{/other_user}", 57 | "gists_url": "https://api.github.com/users/ZeusWPI/gists{/gist_id}", 58 | "starred_url": "https://api.github.com/users/ZeusWPI/starred{/owner}{/repo}", 59 | "subscriptions_url": "https://api.github.com/users/ZeusWPI/subscriptions", 60 | "organizations_url": "https://api.github.com/users/ZeusWPI/orgs", 61 | "repos_url": "https://api.github.com/users/ZeusWPI/repos", 62 | "events_url": "https://api.github.com/users/ZeusWPI/events{/privacy}", 63 | "received_events_url": "https://api.github.com/users/ZeusWPI/received_events", 64 | "type": "Organization", 65 | "site_admin": false 66 | }, 67 | "private": true, 68 | "html_url": "https://github.com/ZeusWPI/glowing-octo-dubstep", 69 | "description": "Top8", 70 | "fork": false, 71 | "url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep", 72 | "forks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/forks", 73 | "keys_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/keys{/key_id}", 74 | "collaborators_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/collaborators{/collaborator}", 75 | "teams_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/teams", 76 | "hooks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/hooks", 77 | "issue_events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/events{/number}", 78 | "events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/events", 79 | "assignees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/assignees{/user}", 80 | "branches_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/branches{/branch}", 81 | "tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/tags", 82 | "blobs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/blobs{/sha}", 83 | "git_tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/tags{/sha}", 84 | "git_refs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/refs{/sha}", 85 | "trees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/trees{/sha}", 86 | "statuses_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/statuses/{sha}", 87 | "languages_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/languages", 88 | "stargazers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/stargazers", 89 | "contributors_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contributors", 90 | "subscribers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscribers", 91 | "subscription_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscription", 92 | "commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/commits{/sha}", 93 | "git_commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/commits{/sha}", 94 | "comments_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/comments{/number}", 95 | "issue_comment_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/comments{/number}", 96 | "contents_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contents/{+path}", 97 | "compare_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/compare/{base}...{head}", 98 | "merges_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/merges", 99 | "archive_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/{archive_format}{/ref}", 100 | "downloads_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/downloads", 101 | "issues_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues{/number}", 102 | "pulls_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/pulls{/number}", 103 | "milestones_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/milestones{/number}", 104 | "notifications_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/notifications{?since,all,participating}", 105 | "labels_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/labels{/name}", 106 | "releases_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/releases{/id}", 107 | "created_at": "2015-03-28T00:35:00Z", 108 | "updated_at": "2015-03-28T02:25:20Z", 109 | "pushed_at": "2015-03-28T02:33:02Z", 110 | "git_url": "git://github.com/ZeusWPI/glowing-octo-dubstep.git", 111 | "ssh_url": "git@github.com:ZeusWPI/glowing-octo-dubstep.git", 112 | "clone_url": "https://github.com/ZeusWPI/glowing-octo-dubstep.git", 113 | "svn_url": "https://github.com/ZeusWPI/glowing-octo-dubstep", 114 | "homepage": "", 115 | "size": 0, 116 | "stargazers_count": 0, 117 | "watchers_count": 0, 118 | "language": null, 119 | "has_issues": true, 120 | "has_downloads": true, 121 | "has_wiki": true, 122 | "has_pages": false, 123 | "forks_count": 0, 124 | "mirror_url": null, 125 | "open_issues_count": 1, 126 | "forks": 0, 127 | "open_issues": 1, 128 | "watchers": 0, 129 | "default_branch": "master" 130 | }, 131 | "organization": { 132 | "login": "ZeusWPI", 133 | "id": 331750, 134 | "url": "https://api.github.com/orgs/ZeusWPI", 135 | "repos_url": "https://api.github.com/orgs/ZeusWPI/repos", 136 | "events_url": "https://api.github.com/orgs/ZeusWPI/events", 137 | "members_url": "https://api.github.com/orgs/ZeusWPI/members{/member}", 138 | "public_members_url": "https://api.github.com/orgs/ZeusWPI/public_members{/member}", 139 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3", 140 | "description": null 141 | }, 142 | "sender": { 143 | "login": "Iasoon", 144 | "id": 6320308, 145 | "avatar_url": "https://avatars.githubusercontent.com/u/6320308?v=3", 146 | "gravatar_id": "", 147 | "url": "https://api.github.com/users/Iasoon", 148 | "html_url": "https://github.com/Iasoon", 149 | "followers_url": "https://api.github.com/users/Iasoon/followers", 150 | "following_url": "https://api.github.com/users/Iasoon/following{/other_user}", 151 | "gists_url": "https://api.github.com/users/Iasoon/gists{/gist_id}", 152 | "starred_url": "https://api.github.com/users/Iasoon/starred{/owner}{/repo}", 153 | "subscriptions_url": "https://api.github.com/users/Iasoon/subscriptions", 154 | "organizations_url": "https://api.github.com/users/Iasoon/orgs", 155 | "repos_url": "https://api.github.com/users/Iasoon/repos", 156 | "events_url": "https://api.github.com/users/Iasoon/events{/privacy}", 157 | "received_events_url": "https://api.github.com/users/Iasoon/received_events", 158 | "type": "User", 159 | "site_admin": false 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /spec/github_jsons/issue_close.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "closed", 3 | "issue": { 4 | "url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1", 5 | "labels_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1/labels{/name}", 6 | "comments_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1/comments", 7 | "events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/1/events", 8 | "html_url": "https://github.com/ZeusWPI/glowing-octo-dubstep/issues/1", 9 | "id": 64936749, 10 | "number": 1, 11 | "title": "Problemen!", 12 | "user": { 13 | "login": "Iasoon", 14 | "id": 6320308, 15 | "avatar_url": "https://avatars.githubusercontent.com/u/6320308?v=3", 16 | "gravatar_id": "", 17 | "url": "https://api.github.com/users/Iasoon", 18 | "html_url": "https://github.com/Iasoon", 19 | "followers_url": "https://api.github.com/users/Iasoon/followers", 20 | "following_url": "https://api.github.com/users/Iasoon/following{/other_user}", 21 | "gists_url": "https://api.github.com/users/Iasoon/gists{/gist_id}", 22 | "starred_url": "https://api.github.com/users/Iasoon/starred{/owner}{/repo}", 23 | "subscriptions_url": "https://api.github.com/users/Iasoon/subscriptions", 24 | "organizations_url": "https://api.github.com/users/Iasoon/orgs", 25 | "repos_url": "https://api.github.com/users/Iasoon/repos", 26 | "events_url": "https://api.github.com/users/Iasoon/events{/privacy}", 27 | "received_events_url": "https://api.github.com/users/Iasoon/received_events", 28 | "type": "User", 29 | "site_admin": false 30 | }, 31 | "labels": [ 32 | 33 | ], 34 | "state": "closed", 35 | "locked": false, 36 | "assignee": null, 37 | "milestone": null, 38 | "comments": 1, 39 | "created_at": "2015-03-28T12:28:07Z", 40 | "updated_at": "2015-03-28T13:03:14Z", 41 | "closed_at": "2015-03-28T13:03:14Z", 42 | "body": "" 43 | }, 44 | "repository": { 45 | "id": 33015564, 46 | "name": "glowing-octo-dubstep", 47 | "full_name": "ZeusWPI/glowing-octo-dubstep", 48 | "owner": { 49 | "login": "ZeusWPI", 50 | "id": 331750, 51 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3", 52 | "gravatar_id": "", 53 | "url": "https://api.github.com/users/ZeusWPI", 54 | "html_url": "https://github.com/ZeusWPI", 55 | "followers_url": "https://api.github.com/users/ZeusWPI/followers", 56 | "following_url": "https://api.github.com/users/ZeusWPI/following{/other_user}", 57 | "gists_url": "https://api.github.com/users/ZeusWPI/gists{/gist_id}", 58 | "starred_url": "https://api.github.com/users/ZeusWPI/starred{/owner}{/repo}", 59 | "subscriptions_url": "https://api.github.com/users/ZeusWPI/subscriptions", 60 | "organizations_url": "https://api.github.com/users/ZeusWPI/orgs", 61 | "repos_url": "https://api.github.com/users/ZeusWPI/repos", 62 | "events_url": "https://api.github.com/users/ZeusWPI/events{/privacy}", 63 | "received_events_url": "https://api.github.com/users/ZeusWPI/received_events", 64 | "type": "Organization", 65 | "site_admin": false 66 | }, 67 | "private": true, 68 | "html_url": "https://github.com/ZeusWPI/glowing-octo-dubstep", 69 | "description": "Top8", 70 | "fork": false, 71 | "url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep", 72 | "forks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/forks", 73 | "keys_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/keys{/key_id}", 74 | "collaborators_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/collaborators{/collaborator}", 75 | "teams_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/teams", 76 | "hooks_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/hooks", 77 | "issue_events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/events{/number}", 78 | "events_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/events", 79 | "assignees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/assignees{/user}", 80 | "branches_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/branches{/branch}", 81 | "tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/tags", 82 | "blobs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/blobs{/sha}", 83 | "git_tags_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/tags{/sha}", 84 | "git_refs_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/refs{/sha}", 85 | "trees_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/trees{/sha}", 86 | "statuses_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/statuses/{sha}", 87 | "languages_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/languages", 88 | "stargazers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/stargazers", 89 | "contributors_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contributors", 90 | "subscribers_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscribers", 91 | "subscription_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/subscription", 92 | "commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/commits{/sha}", 93 | "git_commits_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/git/commits{/sha}", 94 | "comments_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/comments{/number}", 95 | "issue_comment_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues/comments{/number}", 96 | "contents_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/contents/{+path}", 97 | "compare_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/compare/{base}...{head}", 98 | "merges_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/merges", 99 | "archive_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/{archive_format}{/ref}", 100 | "downloads_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/downloads", 101 | "issues_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/issues{/number}", 102 | "pulls_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/pulls{/number}", 103 | "milestones_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/milestones{/number}", 104 | "notifications_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/notifications{?since,all,participating}", 105 | "labels_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/labels{/name}", 106 | "releases_url": "https://api.github.com/repos/ZeusWPI/glowing-octo-dubstep/releases{/id}", 107 | "created_at": "2015-03-28T00:35:00Z", 108 | "updated_at": "2015-03-28T02:25:20Z", 109 | "pushed_at": "2015-03-28T02:33:02Z", 110 | "git_url": "git://github.com/ZeusWPI/glowing-octo-dubstep.git", 111 | "ssh_url": "git@github.com:ZeusWPI/glowing-octo-dubstep.git", 112 | "clone_url": "https://github.com/ZeusWPI/glowing-octo-dubstep.git", 113 | "svn_url": "https://github.com/ZeusWPI/glowing-octo-dubstep", 114 | "homepage": "", 115 | "size": 0, 116 | "stargazers_count": 0, 117 | "watchers_count": 0, 118 | "language": null, 119 | "has_issues": true, 120 | "has_downloads": true, 121 | "has_wiki": true, 122 | "has_pages": false, 123 | "forks_count": 0, 124 | "mirror_url": null, 125 | "open_issues_count": 0, 126 | "forks": 0, 127 | "open_issues": 0, 128 | "watchers": 0, 129 | "default_branch": "master" 130 | }, 131 | "organization": { 132 | "login": "ZeusWPI", 133 | "id": 331750, 134 | "url": "https://api.github.com/orgs/ZeusWPI", 135 | "repos_url": "https://api.github.com/orgs/ZeusWPI/repos", 136 | "events_url": "https://api.github.com/orgs/ZeusWPI/events", 137 | "members_url": "https://api.github.com/orgs/ZeusWPI/members{/member}", 138 | "public_members_url": "https://api.github.com/orgs/ZeusWPI/public_members{/member}", 139 | "avatar_url": "https://avatars.githubusercontent.com/u/331750?v=3", 140 | "description": null 141 | }, 142 | "sender": { 143 | "login": "Iasoon", 144 | "id": 6320308, 145 | "avatar_url": "https://avatars.githubusercontent.com/u/6320308?v=3", 146 | "gravatar_id": "", 147 | "url": "https://api.github.com/users/Iasoon", 148 | "html_url": "https://github.com/Iasoon", 149 | "followers_url": "https://api.github.com/users/Iasoon/followers", 150 | "following_url": "https://api.github.com/users/Iasoon/following{/other_user}", 151 | "gists_url": "https://api.github.com/users/Iasoon/gists{/gist_id}", 152 | "starred_url": "https://api.github.com/users/Iasoon/starred{/owner}{/repo}", 153 | "subscriptions_url": "https://api.github.com/users/Iasoon/subscriptions", 154 | "organizations_url": "https://api.github.com/users/Iasoon/orgs", 155 | "repos_url": "https://api.github.com/users/Iasoon/repos", 156 | "events_url": "https://api.github.com/users/Iasoon/events{/privacy}", 157 | "received_events_url": "https://api.github.com/users/Iasoon/received_events", 158 | "type": "User", 159 | "site_admin": false 160 | } 161 | } 162 | --------------------------------------------------------------------------------