├── vendor └── .keep ├── lib └── tasks │ ├── .keep │ └── db.rake ├── test ├── mailers │ ├── .keep │ ├── previews │ │ └── user_mailer_preview.rb │ └── user_mailer_test.rb ├── models │ ├── .keep │ ├── grade_test.rb │ ├── revision_test.rb │ ├── project_test.rb │ ├── sprint_test.rb │ ├── release_test.rb │ ├── retrospective_test.rb │ ├── user_test.rb │ └── story_test.rb ├── controllers │ ├── .keep │ ├── authentication_controller.rb │ ├── passwords_controller_test.rb │ ├── users_controller_test.rb │ └── releases_controller_test.rb ├── fixtures │ ├── .keep │ ├── files │ │ └── .keep │ ├── revisions.yml │ └── grades.yml ├── integration │ └── .keep ├── test_helper.rb └── helpers │ ├── metric_helper_test.rb │ ├── burndown_helper_test.rb │ └── velocity_helper_test.rb ├── app ├── models │ ├── concerns │ │ └── .keep │ ├── application_record.rb │ ├── grade.rb │ ├── revision.rb │ ├── retrospective.rb │ ├── project.rb │ ├── release.rb │ ├── story.rb │ ├── sprint.rb │ └── user.rb ├── controllers │ ├── concerns │ │ └── .keep │ ├── authentication_controller.rb │ ├── application_controller.rb │ ├── revisions_controller.rb │ ├── retrospectives_controller.rb │ ├── grades_controller.rb │ ├── releases_controller.rb │ ├── passwords_controller.rb │ ├── users_controller.rb │ ├── projects_controller.rb │ ├── stories_controller.rb │ ├── sprints_controller.rb │ └── issues_controller.rb ├── views │ ├── layouts │ │ ├── mailer.text.erb │ │ └── mailer.html.erb │ └── user_mailer │ │ └── recover_password_email.text.erb ├── jobs │ └── application_job.rb ├── mailers │ ├── application_mailer.rb │ └── user_mailer.rb ├── helpers │ ├── date_validation_helper.rb │ ├── burndown_helper.rb │ ├── issue_graphic_helper.rb │ ├── velocity_helper.rb │ ├── metric_helper.rb │ └── validations_helper.rb ├── commands │ ├── json_web_token.rb │ ├── authenticate_user.rb │ └── authorize_api_request.rb ├── docs │ ├── users_doc.rb │ ├── grades_doc.rb │ ├── revisions_doc.rb │ ├── retrospectives_doc.rb │ ├── releases_doc.rb │ ├── projects_doc.rb │ └── stories_doc.rb └── adapters │ └── adapter.rb ├── .dockerignore ├── .travis └── alax-digitalocean-key.enc ├── public └── robots.txt ├── bin ├── bundle ├── rake ├── rails ├── spring ├── update └── setup ├── config ├── spring.rb ├── boot.rb ├── environment.rb ├── initializers │ ├── mime_types.rb │ ├── filter_parameter_logging.rb │ ├── application_controller_renderer.rb │ ├── apipie.rb │ ├── backtrace_silencers.rb │ ├── wrap_parameters.rb │ ├── cors.rb │ └── inflections.rb ├── cable.yml ├── database.yml ├── locales │ └── en.yml ├── application.rb ├── secrets.yml ├── environments │ ├── development.rb │ ├── test.rb │ └── production.rb ├── routes.rb └── puma.rb ├── config.ru ├── db ├── migrate │ ├── 20171030030612_add_token_to_users.rb │ ├── 20171106001427_add_points_to_stories.rb │ ├── 20171210015745_add_issue_id_to_stories.rb │ ├── 20171212131724_remove_github_from_users.rb │ ├── 20171030141822_add_github_slug_to_project.rb │ ├── 20171111174534_add_issue_number_to_stories.rb │ ├── 20171213000135_add_projects_to_grades.rb │ ├── 20171108135629_add_point_stories_to_projects.rb │ ├── 20171030030707_add_user_references_to_projects.rb │ ├── 20171030030842_add_release_references_to_sprints.rb │ ├── 20171030150044_add_sprint_references_to_stories.rb │ ├── 20171105003057_add_sprint_to_retrospectives.rb │ ├── 20171030030755_add_project_references_to_releases.rb │ ├── 20171104121652_add_sprint_references_to_revisions.rb │ ├── 20171030030854_add_dates_to_sprints.rb │ ├── 20171030150130_add_dates_to_stories.rb │ ├── 20171107172605_add_is_project_from_github_to_projects.rb │ ├── 20171030030805_add_dates_to_releases.rb │ ├── 20171030030823_create_sprints.rb │ ├── 20171030030627_create_projects.rb │ ├── 20190418161604_add_password_reset_columns_to_user.rb │ ├── 20171030030739_create_releases.rb │ ├── 20171212174435_create_grades.rb │ ├── 20171030030512_create_users.rb │ ├── 20171030145903_create_stories.rb │ ├── 20171104121638_create_revision.rb │ └── 20171105002927_create_retrospectives.rb └── schema.rb ├── Rakefile ├── HOSTS ├── .gitignore ├── .gitlabci.yml ├── .codeclimate.yml ├── scripts ├── sh │ ├── start-prod.sh │ └── start-dev.sh └── docker │ ├── docker-compose.test.yml │ ├── Dockerfile.dev │ └── docker-compose.prod.yml ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md ├── docker-compose.yml ├── Dockerfile ├── LICENSE ├── .travis.yml ├── Gemfile ├── Makefile ├── .rubocop.yml ├── README.md └── Gemfile.lock /vendor/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | README.md 2 | 3 | .dockerignore 4 | 5 | log/* 6 | tmp/* 7 | .rake_tasks* 8 | coverage/ 9 | -------------------------------------------------------------------------------- /.travis/alax-digitalocean-key.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falko-org/Falko-API/HEAD/.travis/alax-digitalocean-key.enc -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout "mailer" 4 | end 5 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__) 3 | load Gem.bin_path("bundler", "bundle") 4 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ).each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /test/mailers/previews/user_mailer_preview.rb: -------------------------------------------------------------------------------- 1 | # Preview all emails at http://localhost:3000/rails/mailers/user_mailer 2 | class UserMailerPreview < ActionMailer::Preview 3 | end 4 | -------------------------------------------------------------------------------- /test/mailers/user_mailer_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class UserMailerTest < ActionMailer::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20171030030612_add_token_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddTokenToUsers < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :users, :access_token, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171106001427_add_points_to_stories.rb: -------------------------------------------------------------------------------- 1 | class AddPointsToStories < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :stories, :story_points, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171210015745_add_issue_id_to_stories.rb: -------------------------------------------------------------------------------- 1 | class AddIssueIdToStories < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :stories, :issue_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171212131724_remove_github_from_users.rb: -------------------------------------------------------------------------------- 1 | class RemoveGithubFromUsers < ActiveRecord::Migration[5.1] 2 | def change 3 | remove_column :users, :github, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20171030141822_add_github_slug_to_project.rb: -------------------------------------------------------------------------------- 1 | class AddGithubSlugToProject < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :projects, :github_slug, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171111174534_add_issue_number_to_stories.rb: -------------------------------------------------------------------------------- 1 | class AddIssueNumberToStories < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :stories, :issue_number, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171213000135_add_projects_to_grades.rb: -------------------------------------------------------------------------------- 1 | class AddProjectsToGrades < ActiveRecord::Migration[5.1] 2 | def change 3 | add_reference :grades, :project, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171108135629_add_point_stories_to_projects.rb: -------------------------------------------------------------------------------- 1 | class AddPointStoriesToProjects < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :projects, :is_scoring, :boolean 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: redis://localhost:6379/1 10 | channel_prefix: falko20172_production 11 | -------------------------------------------------------------------------------- /db/migrate/20171030030707_add_user_references_to_projects.rb: -------------------------------------------------------------------------------- 1 | class AddUserReferencesToProjects < ActiveRecord::Migration[5.1] 2 | def change 3 | add_reference :projects, :user, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171030030842_add_release_references_to_sprints.rb: -------------------------------------------------------------------------------- 1 | class AddReleaseReferencesToSprints < ActiveRecord::Migration[5.1] 2 | def change 3 | add_reference :sprints, :release, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171030150044_add_sprint_references_to_stories.rb: -------------------------------------------------------------------------------- 1 | class AddSprintReferencesToStories < ActiveRecord::Migration[5.1] 2 | def change 3 | add_reference :stories, :sprint, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171105003057_add_sprint_to_retrospectives.rb: -------------------------------------------------------------------------------- 1 | class AddSprintToRetrospectives < ActiveRecord::Migration[5.1] 2 | def change 3 | add_reference :retrospectives, :sprint, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/user_mailer/recover_password_email.text.erb: -------------------------------------------------------------------------------- 1 | Hello <%=@user.name%>! 2 | 3 | Someone has requested to reset the password of your account. 4 | 5 | To reset your password click the following link: 6 | 7 | <%=@uri%> 8 | 9 | -------------------------------------------------------------------------------- /db/migrate/20171030030755_add_project_references_to_releases.rb: -------------------------------------------------------------------------------- 1 | class AddProjectReferencesToReleases < ActiveRecord::Migration[5.1] 2 | def change 3 | add_reference :releases, :project, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171104121652_add_sprint_references_to_revisions.rb: -------------------------------------------------------------------------------- 1 | class AddSprintReferencesToRevisions < ActiveRecord::Migration[5.1] 2 | def change 3 | add_reference :revisions, :sprint, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/grade.rb: -------------------------------------------------------------------------------- 1 | class Grade < ApplicationRecord 2 | belongs_to :project 3 | 4 | validates :weight_burndown, presence: true 5 | validates :weight_velocity, presence: true 6 | validates :weight_debts, presence: true 7 | end 8 | -------------------------------------------------------------------------------- /app/models/revision.rb: -------------------------------------------------------------------------------- 1 | class Revision < ApplicationRecord 2 | belongs_to :sprint 3 | 4 | validates :done_report, presence: true, length: { maximum: 500 } 5 | validates :undone_report, presence: true, length: { maximum: 500 } 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20171030030854_add_dates_to_sprints.rb: -------------------------------------------------------------------------------- 1 | class AddDatesToSprints < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :sprints, :initial_date, :date 4 | add_column :sprints, :final_date, :date 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20171030150130_add_dates_to_stories.rb: -------------------------------------------------------------------------------- 1 | class AddDatesToStories < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :stories, :initial_date, :date 4 | add_column :stories, :final_date, :date 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20171107172605_add_is_project_from_github_to_projects.rb: -------------------------------------------------------------------------------- 1 | class AddIsProjectFromGithubToProjects < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :projects, :is_project_from_github, :boolean 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20171030030805_add_dates_to_releases.rb: -------------------------------------------------------------------------------- 1 | class AddDatesToReleases < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :releases, :initial_date, :date 4 | add_column :releases, :final_date, :date 5 | end 6 | end 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 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?("spring") 6 | end 7 | require_relative "../config/boot" 8 | require "rake" 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /db/migrate/20171030030823_create_sprints.rb: -------------------------------------------------------------------------------- 1 | class CreateSprints < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :sprints do |t| 4 | t.string :name 5 | t.text :description 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/fixtures/revisions.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | done_report: [MyText] 5 | undone_report: [MyText] 6 | 7 | two: 8 | done_report: [MyText] 9 | undone_report: [MyText] 10 | -------------------------------------------------------------------------------- /db/migrate/20171030030627_create_projects.rb: -------------------------------------------------------------------------------- 1 | class CreateProjects < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :projects do |t| 4 | t.string :name 5 | t.text :description 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /HOSTS: -------------------------------------------------------------------------------- 1 | Homolog Environment 2 | host 138.197.16.42 3 | HostName 138.197.16.42 4 | IdentityFile your/ssh-key/path 5 | User root 6 | 7 | Production Environment 8 | host 104.236.12.198 9 | HostName 104.236.12.198 10 | IdentityFile your/ssh-key/path 11 | User root 12 | -------------------------------------------------------------------------------- /db/migrate/20190418161604_add_password_reset_columns_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddPasswordResetColumnsToUser < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :users, :reset_password_token, :string 4 | add_column :users, :reset_password_sent_at, :datetime 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/fixtures/grades.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | weight_burndown: 5 | weight_velocity: 6 | weight_debts: 7 | 8 | two: 9 | weight_burndown: 10 | weight_velocity: 11 | weight_debts: 12 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /db/migrate/20171030030739_create_releases.rb: -------------------------------------------------------------------------------- 1 | class CreateReleases < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :releases do |t| 4 | t.string :name 5 | t.text :description 6 | t.integer :amount_of_sprints 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20171212174435_create_grades.rb: -------------------------------------------------------------------------------- 1 | class CreateGrades < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :grades do |t| 4 | t.float :weight_burndown 5 | t.float :weight_velocity 6 | t.float :weight_debts 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /db/migrate/20171030030512_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :users do |t| 4 | t.string :name 5 | t.string :email 6 | t.string :password_digest 7 | t.string :github 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /config/initializers/apipie.rb: -------------------------------------------------------------------------------- 1 | Apipie.configure do |config| 2 | config.app_name = "Falko20172" 3 | config.api_base_url = "" 4 | config.doc_base_url = "/apipie" 5 | config.translate = false 6 | config.api_controllers_matcher = "#{Rails.root}/app/controllers/**/*.rb" 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20171030145903_create_stories.rb: -------------------------------------------------------------------------------- 1 | class CreateStories < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :stories do |t| 4 | t.string :name 5 | t.text :description 6 | t.string :assign 7 | t.string :pipeline 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20171104121638_create_revision.rb: -------------------------------------------------------------------------------- 1 | class CreateRevision < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :revisions do |t| 4 | t.text :done_report, array: true, defaut: [] 5 | t.text :undone_report, array: true, defaut: [] 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/mailers/user_mailer.rb: -------------------------------------------------------------------------------- 1 | class UserMailer < ApplicationMailer 2 | default from: "noreply@falko.com" 3 | 4 | def recover_password_email 5 | @user = params[:user] 6 | @uri = ENV["PASSWORD_RESET_ADDRESS"].gsub(//, @user.reset_password_token) 7 | mail(to: @user.email, subject: "Falko password recovery") 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/helpers/date_validation_helper.rb: -------------------------------------------------------------------------------- 1 | module DateValidationHelper 2 | def is_final_date_valid? 3 | # Verifies if dates exist, and if final date is after initial date 4 | if self[:initial_date] && self[:final_date] && self[:final_date] < self[:initial_date] 5 | errors.add(:final_date, "Final date cannot be in the past") 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/models/retrospective.rb: -------------------------------------------------------------------------------- 1 | class Retrospective < ApplicationRecord 2 | belongs_to :sprint 3 | 4 | validates :sprint_report, length: { maximum: 1500 } 5 | validates :positive_points, presence: true, length: { maximum: 500 } 6 | validates :negative_points, presence: true, length: { maximum: 500 } 7 | validates :improvements, presence: true, length: { maximum: 500 } 8 | end 9 | -------------------------------------------------------------------------------- /lib/tasks/db.rake: -------------------------------------------------------------------------------- 1 | namespace :db do 2 | desc "Checks whether the database exists or not" 3 | task :exists do 4 | begin 5 | # Tries to initialize the application. 6 | # It will fail if the database does not exist 7 | Rake::Task["environment"].invoke 8 | ActiveRecord::Base.connection 9 | rescue 10 | exit 1 11 | else 12 | exit 0 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20171105002927_create_retrospectives.rb: -------------------------------------------------------------------------------- 1 | class CreateRetrospectives < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :retrospectives do |t| 4 | t.text :sprint_report 5 | t.text :positive_points, array: true, default: [] 6 | t.text :negative_points, array: true, default: [] 7 | t.text :improvements, array: true, default: [] 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/models/project.rb: -------------------------------------------------------------------------------- 1 | class Project < ApplicationRecord 2 | belongs_to :user 3 | has_many :releases, dependent: :destroy 4 | has_one :grade, dependent: :destroy 5 | 6 | validates :name, presence: true, length: { maximum: 128, minimum: 2 } 7 | validates :description, length: { maximum: 256 } 8 | validates :is_project_from_github, inclusion: { in: [true, false] } 9 | validates :is_scoring, inclusion: { in: [true, false] } 10 | end 11 | -------------------------------------------------------------------------------- /app/models/release.rb: -------------------------------------------------------------------------------- 1 | class Release < ApplicationRecord 2 | include DateValidationHelper 3 | 4 | belongs_to :project 5 | has_many :sprints, dependent: :destroy 6 | 7 | validates :name, presence: true, length: { maximum: 80, minimum: 2 } 8 | validates :description, length: { maximum: 256 } 9 | validates :initial_date, presence: true 10 | validates :final_date, presence: true 11 | 12 | validate :is_final_date_valid? 13 | end 14 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | if ENV["RAILS_ENV"] == "test" 4 | require "simplecov" 5 | SimpleCov.start "rails" 6 | puts "required simplecov" 7 | end 8 | 9 | begin 10 | load File.expand_path("../spring", __FILE__) 11 | rescue LoadError => e 12 | raise unless e.message.include?("spring") 13 | end 14 | APP_PATH = File.expand_path("../config/application", __dir__) 15 | require_relative "../config/boot" 16 | require "rails/commands" 17 | -------------------------------------------------------------------------------- /app/commands/json_web_token.rb: -------------------------------------------------------------------------------- 1 | class JsonWebToken 2 | class << self 3 | def encode(payload, exp = 24.hours.from_now) 4 | payload[:exp] = exp.to_i 5 | JWT.encode(payload, Rails.application.secrets.secret_key_base) 6 | end 7 | 8 | def decode(token) 9 | body = JWT.decode(token, Rails.application.secrets.secret_key_base)[0] 10 | HashWithIndifferentAccess.new body 11 | rescue 12 | nil 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore my Continuous Deploy Keys/Credentials 2 | .travis/*.key 3 | .travis/*.key.pub 4 | .travis/*.pem 5 | 6 | # Ignore bundler config. 7 | /.bundle 8 | 9 | # Ignore all logfiles and tempfiles. 10 | log/* 11 | tmp/* 12 | !/log/.keep 13 | !/tmp/.keep 14 | *.log 15 | log/ 16 | tmp/ 17 | 18 | #Ignore databases 19 | *.sql 20 | 21 | #Ignore vim temporary files 22 | *.sw* 23 | *.swp* 24 | 25 | #Ignore code editor configurantion files 26 | .vscode/ 27 | 28 | vendor/bundle 29 | coverage/ 30 | -------------------------------------------------------------------------------- /app/models/story.rb: -------------------------------------------------------------------------------- 1 | class Story < ApplicationRecord 2 | include DateValidationHelper 3 | 4 | belongs_to :sprint 5 | 6 | validates :name, presence: true, length: { maximum: 128, minimum: 2 } 7 | validates :description, length: { maximum: 8000 } 8 | validates :assign, length: { maximum: 32, minimum: 2 }, allow_blank: true 9 | validates :pipeline, length: { maximum: 16, minimum: 4 } 10 | validates :initial_date, presence: true 11 | validates :issue_number, presence: true 12 | validate :is_final_date_valid? 13 | end 14 | -------------------------------------------------------------------------------- /app/models/sprint.rb: -------------------------------------------------------------------------------- 1 | class Sprint < ApplicationRecord 2 | include DateValidationHelper 3 | 4 | belongs_to :release 5 | has_many :stories, dependent: :destroy 6 | has_one :revision, dependent: :destroy 7 | has_one :retrospective, dependent: :destroy 8 | 9 | validates :name, presence: true, length: { maximum: 128, minimum: 2 } 10 | validates :description, length: { maximum: 256 } 11 | validates :initial_date, presence: true 12 | validates :final_date, presence: true 13 | 14 | validate :is_final_date_valid? 15 | end 16 | -------------------------------------------------------------------------------- /app/controllers/authentication_controller.rb: -------------------------------------------------------------------------------- 1 | class AuthenticationController < ApplicationController 2 | skip_before_action :authenticate_request 3 | def authenticate 4 | command = AuthenticateUser.call(params[:email], params[:password]) 5 | 6 | if command.success? 7 | user = User.find_by_email params[:email] 8 | render json: { auth_token: command.result, user: user.as_json(only: [:id, :name, :email]) } 9 | else 10 | render json: { error: command.errors } , status: :unauthorized 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::API 2 | include ValidationsHelper 3 | 4 | before_action :authenticate_request, :set_default_request_format 5 | attr_reader :current_user 6 | 7 | private 8 | 9 | def authenticate_request 10 | @current_user = AuthorizeApiRequest.call(request.headers).result 11 | render json: { error: "Not Authorized" }, status: 401 unless @current_user 12 | end 13 | 14 | def set_default_request_format 15 | request.format = :json 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/controllers/authentication_controller.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class AuthenticationControllerTest < ActionDispatch::IntegrationTest 4 | test "should authenticate user" do 5 | @user = User.create! name: "teste user", 6 | email: "test-user@mail.com", 7 | email_confirmation: "test-user@mail.com", 8 | password: "test-user" 9 | 10 | post :authenticate, params: { email: "test-user@mail.com", password: "test-user" } 11 | assert_response :success 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /.gitlabci.yml: -------------------------------------------------------------------------------- 1 | image: ruby:2.4.1 2 | 3 | services: 4 | - postgres:9.6 5 | 6 | variables: 7 | PG_USERNAME: "postgres" 8 | PG_DATABASE: "travis_ci_test" 9 | PG_HOST: "postgres" 10 | RAILS_ENV: "test" 11 | 12 | cache: 13 | paths: 14 | - vendor/ruby 15 | 16 | rubocop: 17 | script: 18 | - rubocop 19 | 20 | rails: 21 | variables: 22 | DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/$PG_DATABASE" 23 | script: 24 | - bundle install 25 | - rails db:create 26 | - rails db:migrate 27 | - rails db:test:prepare 28 | - rails test 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 to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require "rubygems" 8 | require "bundler" 9 | 10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem "spring", spring.version 15 | require "spring/binstub" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/commands/authenticate_user.rb: -------------------------------------------------------------------------------- 1 | class AuthenticateUser 2 | prepend SimpleCommand 3 | 4 | def initialize(email, password) 5 | @email = email 6 | @password = password 7 | end 8 | 9 | def call 10 | JsonWebToken.encode(user_id: user.id) if user 11 | end 12 | 13 | private 14 | attr_accessor :email, :password 15 | 16 | def user 17 | user = User.find_by_email(email) 18 | return user if user && user.authenticate(password) 19 | 20 | errors.add :user_authentication, "invalid credentials" 21 | nil 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /config/initializers/cors.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Avoid CORS issues when API is called from the frontend app. 4 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. 5 | 6 | # Read more: https://github.com/cyu/rack-cors 7 | 8 | Rails.application.config.middleware.insert_before 0, Rack::Cors do 9 | allow do 10 | origins ENV.fetch("ALLOWED_ORIGINS") { "*" } 11 | 12 | resource "*", 13 | headers: :any, 14 | methods: [:get, :post, :put, :patch, :delete, :options, :head] 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | brakeman: 4 | enabled: true 5 | checks: 6 | session_secret: 7 | enabled: false 8 | bundler-audit: 9 | enabled: true 10 | duplication: 11 | enabled: true 12 | config: 13 | languages: 14 | - ruby 15 | - javascript 16 | fixme: 17 | enabled: true 18 | rubocop: 19 | enabled: true 20 | ratings: 21 | paths: 22 | - Gemfile.lock 23 | - "**.erb" 24 | - "**.rb" 25 | - "**.rhtml" 26 | - "**.slim" 27 | - "**.inc" 28 | - "**.js" 29 | - "**.jsx" 30 | - "**.module" 31 | exclude_paths: 32 | - config/ 33 | - db/ 34 | - test/ 35 | - vendor/ 36 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: postgresql 3 | encoding: unicode 4 | pool: 5 5 | username: <%= ENV["PG_USERNAME"] %> 6 | password: 7 | 8 | development: 9 | <<: *default 10 | database: <%= ENV["PG_DATABASE"] %> 11 | host: <%= ENV["PG_HOST"] %> 12 | 13 | test: 14 | <<: *default 15 | database: <%= ENV["PG_DATABASE"] %> 16 | host: <%= ENV["PG_HOST"] %> 17 | 18 | production: 19 | adapter: postgresql 20 | database: Falko-2017_2-BackEnd_production 21 | encoding: unicode 22 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 23 | username: postgres 24 | password: 25 | url: <%= ENV["DATABASE_URL"] %> 26 | host: dokku.postgres.falko-database 27 | -------------------------------------------------------------------------------- /scripts/sh/start-prod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bundle check || bundle install 4 | 5 | while ! pg_isready -h dokku.postgres.falko-database -p 5432 -q -U postgres; do 6 | >&2 echo "Postgres is unavailable - sleeping" 7 | sleep 1 8 | done 9 | 10 | >&2 echo "Postgres is up - executing command" 11 | 12 | if bundle exec rake db:exists; then 13 | bundle exec rake db:migrate 14 | else 15 | bundle exec rake db:create 16 | bundle exec rails db:migrate 17 | fi 18 | 19 | pidfile='/Falko-2017.2-BackEnd/tmp/pids/server.pid' 20 | 21 | if [ -f $pidfile ] ; then 22 | echo 'Server PID file already exists. Removing it...' 23 | rm $pidfile 24 | fi 25 | 26 | bundle exec puma -C config/puma.rb 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## ** ***Question 2 | 3 | Describe your question with details, if possible add screenshots and such. 4 | 5 | ## * **Expected Behaviour 6 | 7 | Describe the behaviour the system should have. 8 | 9 | ## * **Current Behaviour 10 | 11 | Describe the system's current behaviour. 12 | 13 | ## * **Steps to reproduce unexpected behaviour 14 | 15 | In case of bugs, tell us the steps to reach the problem. 16 | 17 | ## Checklist 18 | 19 | - [ ] Issue has labels. 20 | - [ ] Issue has screenshots if required. 21 | - [ ] Issue has a significant name. 22 | 23 | *Remove if it is a question 24 | 25 | **Remove if it is a request for a new feature 26 | 27 | ***Remove if it is a bug -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /scripts/docker/docker-compose.test.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | 4 | db: 5 | container_name: falko-database-test 6 | image: postgres:9.6 7 | 8 | api: 9 | container_name: falko-api-test 10 | image: alaxalves/api:1.7 11 | command: ["sh", "scripts/sh/start-dev.sh"] 12 | environment: 13 | - PG_USERNAME=postgres 14 | - PG_DATABASE=travis_ci_test 15 | - PG_HOST=falko-database-test 16 | - PG_PORT=5432 17 | - RAILS_ENV=test 18 | - CLIENT_ID=1254ef5e2765397c4fb4 19 | - CLIENT_SECRET=c566f60e74a49bd8e664033e2978a31d3b39748d 20 | - PASSWORD_RESET_ADDRESS=http://localhost:8080/#/users/resetpassword?token= 21 | volumes: 22 | - .:/Falko-2017.2-BackEnd 23 | depends_on: 24 | - db 25 | -------------------------------------------------------------------------------- /scripts/docker/Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM ruby:2.4.1 2 | 3 | MAINTAINER alaxallves@gmail.com 4 | 5 | RUN apt-get update -qq \ 6 | && apt-get install -y build-essential libpq-dev \ 7 | && curl -sL https://deb.nodesource.com/setup_8.x | bash - \ 8 | && apt-get install -y nodejs \ 9 | && apt-get update && apt-get install -y curl apt-transport-https wget \ 10 | && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ 11 | && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ 12 | && apt-get update && apt-get install -y yarn postgresql-client 13 | 14 | ENV BUNDLE_PATH /bundle-cache 15 | 16 | WORKDIR /Falko-2017.2-BackEnd 17 | 18 | COPY . /Falko-2017.2-BackEnd 19 | 20 | CMD ["bundle", "exec", "rails", "s", "-p", "3000" "-b", "0.0.0.0"] 21 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require "simplecov" 2 | require File.expand_path("../../config/environment", __FILE__) 3 | require "rails/test_help" 4 | require "minitest/autorun" 5 | require "minitest/reporters" 6 | 7 | ENV["RAILS_ENV"] ||= "test" 8 | 9 | SimpleCov.start "rails" do 10 | add_group "Validators", "app/validators" 11 | add_filter "/config/" 12 | add_filter "/lib/tasks" 13 | add_filter "/test/" 14 | add_filter "/vendor/" 15 | end 16 | 17 | SimpleCov.command_name "test:units" 18 | 19 | SimpleCov.formatters = [SimpleCov::Formatter::HTMLFormatter] 20 | 21 | Minitest::Reporters.use! 22 | 23 | class ActiveSupport::TestCase 24 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 25 | fixtures :all 26 | 27 | # Add more helper methods to be used by all tests here... 28 | end 29 | -------------------------------------------------------------------------------- /app/commands/authorize_api_request.rb: -------------------------------------------------------------------------------- 1 | class AuthorizeApiRequest 2 | prepend SimpleCommand 3 | def initialize(headers = {}) 4 | @headers = headers 5 | end 6 | 7 | def call 8 | user 9 | end 10 | 11 | private 12 | 13 | attr_reader :headers 14 | 15 | def user 16 | @user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token 17 | @user || errors.add(:token, "Invalid token") && nil 18 | end 19 | 20 | def decoded_auth_token 21 | @decoded_auth_token ||= JsonWebToken.decode(http_auth_header) 22 | end 23 | 24 | def http_auth_header 25 | if headers["Authorization"].present? 26 | return headers["Authorization"].split(" ").last 27 | else 28 | errors.add(:token, "Missing token") 29 | end 30 | nil 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /scripts/sh/start-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bundle check || bundle install 4 | 5 | while ! pg_isready -h $PG_HOST -p $PG_PORT -q -U $PG_USERNAME; do 6 | >&2 echo "Postgres is unavailable - sleeping..." 7 | sleep 1 8 | done 9 | 10 | >&2 echo "Postgres is up - executing commands..." 11 | 12 | if bundle exec rake db:exists; then 13 | >&2 echo "Database exists, only migrating..." 14 | bundle exec rake db:migrate 15 | else 16 | >&2 echo "Database doesn't exist, creating and migrating it..." 17 | bundle exec rake db:create 18 | bundle exec rake db:migrate 19 | fi 20 | 21 | bundle exec rake db:seed 22 | 23 | pidfile='/Falko-2017.2-BackEnd/tmp/pids/server.pid' 24 | 25 | if [ -f $pidfile ] ; then 26 | echo 'Server PID file already exists. Removing it...' 27 | rm $pidfile 28 | fi 29 | 30 | bundle exec rails s -p 3000 -b 0.0.0.0 31 | -------------------------------------------------------------------------------- /scripts/docker/docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | 4 | falko-database: 5 | container_name: dokku.postgres.falko 6 | image: postgres:9.6 7 | environment: 8 | - PG_HOST=falko-database 9 | - PG_USERNAME=postgres 10 | volumes: 11 | - ./tmp/data/postgresql:/postgresql 12 | - ./tmp/data/postgresql:/var/lib/postgresql 13 | 14 | falko-server: 15 | container_name: falko-server 16 | build: 17 | context: . 18 | dockerfile: Dockerfile.prod 19 | environment: 20 | - RAILS_ENV=production 21 | - CLIENT_ID=cbd5f91719282354f09b 22 | - CLIENT_SECRET=634dd13c943b8196d4345334031c43d6d5a75fc8 23 | volumes: 24 | - .:/Falko-2017.2-BackEnd 25 | - bundle-cache:/bundle-cache 26 | ports: 27 | - 3000 28 | depends_on: 29 | - falko-database 30 | 31 | volumes: 32 | bundle-cache: 33 | 34 | 35 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "pathname" 3 | require "fileutils" 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path("../../", __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts "== Installing dependencies ==" 18 | system! "gem install bundler --conservative" 19 | system("bundle check") || system!("bundle install") 20 | 21 | puts "\n== Updating database ==" 22 | system! "bin/rails db:migrate" 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! "bin/rails log:clear tmp:clear" 26 | 27 | puts "\n== Restarting application server ==" 28 | system! "bin/rails restart" 29 | end 30 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Proposed Changes 2 | 3 | Describe briefly your changes. Be sure you've tracked this pull request to an issue. 4 | 5 | ## Type of Change 6 | 7 | What kind of changes this Pull Request introduces to Falko? 8 | _`x` to what it applies_ 9 | 10 | - [ ] Bugfix 11 | - [ ] New Feature 12 | 13 | ## Checklist 14 | 15 | _`x`to what it applies. If not sure about some topics, don't hesitate to ask. We're here to help!_ 16 | 17 | - [ ] Pull Request's name is self-explanatory and its in English. 18 | - [ ] There are no _Rubocop_ issues. 19 | - [ ] Travis build has success (tests, code climate and codacy checks). 20 | - [ ] Pull Request is linked to an existing issue. 21 | - [ ] Seeds have been created to the new User Stories. 22 | 23 | ## Other Comments 24 | If it's a change relatively big/complex and/or you have any comment/question to do, feel free to explain or comment anything you feel relevant. -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | has_many :projects, dependent: :destroy 3 | 4 | validates :name, presence: true, length: { in: 3..80 }, uniqueness: true 5 | validates :email, confirmation: true, presence: true, uniqueness: true 6 | validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i 7 | validates_presence_of :password_digest 8 | 9 | has_secure_password 10 | 11 | def generate_password_token! 12 | self.reset_password_token = generate_token 13 | self.reset_password_sent_at = Time.now.utc 14 | save! 15 | end 16 | 17 | def password_token_valid? 18 | (self.reset_password_sent_at + 4.hours) > Time.now.utc 19 | end 20 | 21 | def reset_password!(password) 22 | self.reset_password_token = nil 23 | self.password = password 24 | save! 25 | end 26 | 27 | private 28 | 29 | 30 | def generate_token 31 | SecureRandom.hex(10) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | services: 3 | 4 | db: 5 | container_name: falko-database 6 | image: postgres:9.6 7 | networks: 8 | - falko-backend 9 | 10 | api: 11 | container_name: falko-api 12 | image: alaxalves/api:1.7 13 | command: ["sh", "scripts/sh/start-dev.sh"] 14 | environment: 15 | - PG_USERNAME=postgres 16 | - PG_DATABASE=falko-dev-db 17 | - PG_HOST=falko-database 18 | - PG_PORT=5432 19 | - RAILS_ENV=development 20 | - CLIENT_ID=1254ef5e2765397c4fb4 21 | - CLIENT_SECRET=c566f60e74a49bd8e664033e2978a31d3b39748d 22 | - PASSWORD_RESET_ADDRESS=http://localhost:8080/#/users/resetpassword?token= 23 | volumes: 24 | - .:/Falko-2017.2-BackEnd 25 | - bundle-cache:/bundle-cache 26 | networks: 27 | - falko-backend 28 | ports: 29 | - 3000:3000 30 | depends_on: 31 | - db 32 | 33 | volumes: 34 | bundle-cache: 35 | name: bundle-cache 36 | 37 | networks: 38 | falko-backend: 39 | name: falko-backend 40 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "pathname" 3 | require "fileutils" 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path("../../", __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts "== Installing dependencies ==" 18 | system! "gem install bundler --conservative" 19 | system("bundle check") || system!("bundle install") 20 | 21 | 22 | # puts "\n== Copying sample files ==" 23 | # unless File.exist?('config/database.yml') 24 | # cp 'config/database.yml.sample', 'config/database.yml' 25 | # end 26 | 27 | puts "\n== Preparing database ==" 28 | system! "bin/rails db:setup" 29 | 30 | puts "\n== Removing old logs and tempfiles ==" 31 | system! "bin/rails log:clear tmp:clear" 32 | 33 | puts "\n== Restarting application server ==" 34 | system! "bin/rails restart" 35 | end 36 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a build. 11 | 2. Update the README.md with details of changes to the interface, this includes new environment variables exposed ports, useful file locations and container parameters. 12 | 3. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent. 13 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you. 14 | 15 | ## Code of Conduct 16 | 17 | - You can find useful info about our code of conduct at CODE_OF_CONDUCT.md, found at this project root. 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.4.1 2 | 3 | MAINTAINER alaxallves@gmail.com 4 | 5 | RUN apt-get update -qq && apt-get install -y build-essential libpq-dev 6 | 7 | RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - \ 8 | && apt-get install -y nodejs 9 | 10 | RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ 11 | curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ 12 | echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ 13 | apt-get update && apt-get install -y yarn postgresql-client 14 | 15 | WORKDIR /Falko-2017.2-BackEnd 16 | 17 | ENV RAILS_ENV=production 18 | 19 | ENV BUNDLE_PATH /bundle-cache 20 | 21 | # Client ID provided by the GitHub Prod app 22 | ENV CLIENT_ID=cbd5f91719282354f09b 23 | 24 | # Client secret provided by the GitHub Prod app 25 | ENV CLIENT_SECRET=634dd13c943b8196d4345334031c43d6d5a75fc8 26 | 27 | COPY . /Falko-2017.2-BackEnd 28 | 29 | COPY Gemfile /Falko-2017.2-BackEnd/Gemfile 30 | COPY Gemfile.lock /Falko-2017.2-BackEnd/Gemfile.lock 31 | 32 | EXPOSE 3000 33 | 34 | ENTRYPOINT ["./start-prod.sh"] 35 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Falko Organization 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. 22 | -------------------------------------------------------------------------------- /app/controllers/revisions_controller.rb: -------------------------------------------------------------------------------- 1 | class RevisionsController < ApplicationController 2 | include ValidationsHelper 3 | include RevisionsDoc 4 | 5 | before_action :set_revision, only: [:show, :update] 6 | 7 | before_action only: [:index, :create] do 8 | validate_sprint(0, :sprint_id) 9 | end 10 | 11 | before_action only: [:show, :update, :destroy] do 12 | validate_sprint_dependencies(:id, "revision") 13 | end 14 | 15 | def index 16 | @revision = @sprint.revision 17 | render json: @revision 18 | end 19 | 20 | def create 21 | create_sprint_dependencies("revision", revision_params) 22 | end 23 | 24 | def show 25 | render json: @revision 26 | end 27 | 28 | def update 29 | if @revision.update(revision_params) 30 | render json: @revision 31 | else 32 | render json: @revision.errors, status: :unprocessable_entity 33 | end 34 | end 35 | 36 | def destroy 37 | @revision.destroy 38 | end 39 | 40 | private 41 | def set_revision 42 | @revision = Revision.find(params[:id]) 43 | end 44 | 45 | def revision_params 46 | params.require(:revision).permit(done_report: [], undone_report: []) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | require "active_job/railtie" 7 | require "active_record/railtie" 8 | require "action_controller/railtie" 9 | require "action_mailer/railtie" 10 | require "action_view/railtie" 11 | # require "action_cable/engine" 12 | # require "sprockets/railtie" 13 | require "rails/test_unit/railtie" 14 | 15 | # Require the gems listed in Gemfile, including any gems 16 | # you've limited to :test, :development, or :production. 17 | Bundler.require(*Rails.groups) 18 | 19 | module Falko20172 20 | class Application < Rails::Application 21 | # Initialize configuration defaults for originally generated Rails version. 22 | config.load_defaults 5.1 23 | config.autoload_paths << Rails.root.join("lib") 24 | # Settings in config/environments/* take precedence over those specified here. 25 | # Application configuration should go into files in config/initializers 26 | # -- all .rb files in that directory are automatically loaded. 27 | 28 | # Only loads a smaller set of middleware suitable for API only apps. 29 | # Middleware like session, flash, cookies can be added back manually. 30 | # Skip views, helpers and assets when generating a new resource. 31 | config.api_only = true 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: generic 4 | 5 | env: 6 | global: 7 | - RAILS_ENV=test 8 | - CC_TEST_REPORTER_ID=095bc7916972a84272890f2c79eaba7fd64124a4b5b4e826ba0a4d0599b6bfe3 9 | 10 | services: 11 | - docker 12 | 13 | before_script: 14 | - mv scripts/docker/docker-compose.test.yml . 15 | - docker-compose -f docker-compose.test.yml up -d 16 | - docker exec falko-api-test bash -c "curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > cc-test-reporter" 17 | - docker exec falko-api-test bash -c "chmod +x cc-test-reporter" 18 | - docker exec falko-api-test bash -c "./cc-test-reporter before-build" 19 | 20 | install: true 21 | 22 | addons: 23 | code_climate: 24 | repo_token: "095bc7916972a84272890f2c79eaba7fd64124a4b5b4e826ba0a4d0599b6bfe3" 25 | 26 | script: 27 | - > 28 | while ! docker logs falko-api-test | grep "Listening on tcp://0.0.0.0:3000"; do 29 | echo "Waiting for start script to finish..." 30 | sleep 5 31 | done; 32 | - docker exec -it falko-api-test bundle exec rails db:test:prepare 33 | - docker exec -it falko-api-test bundle exec rails test 34 | - docker exec -it falko-api-test bundle exec rubocop 35 | 36 | after_script: 37 | - docker exec -it falko-api-test bash -c "./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT --coverage-input-type simplecov --id $CC_TEST_REPORTER_ID" 38 | - docker-compose down --remove-orphans 39 | -------------------------------------------------------------------------------- /app/controllers/retrospectives_controller.rb: -------------------------------------------------------------------------------- 1 | class RetrospectivesController < ApplicationController 2 | include ValidationsHelper 3 | include RetrospectivesDoc 4 | 5 | before_action :set_retrospective, only: [:show, :update, :destroy] 6 | 7 | before_action only: [:index, :create] do 8 | validate_sprint(0, :sprint_id) 9 | end 10 | 11 | before_action only: [:show, :update, :destroy] do 12 | validate_sprint_dependencies(:id, "retrospective") 13 | end 14 | 15 | 16 | def index 17 | @retrospective = @sprint.retrospective 18 | 19 | if @retrospective == nil 20 | @retrospective = [] 21 | end 22 | 23 | render json: @retrospective 24 | end 25 | 26 | def create 27 | create_sprint_dependencies("retrospective", retrospective_params) 28 | end 29 | 30 | def show 31 | render json: @retrospective 32 | end 33 | 34 | def update 35 | if @retrospective.update(retrospective_params) 36 | render json: @retrospective 37 | else 38 | render json: @retrospective.errors, status: :unprocessable_entity 39 | end 40 | end 41 | 42 | def destroy 43 | @retrospective.destroy 44 | end 45 | 46 | private 47 | def set_retrospective 48 | @retrospective = Retrospective.find(params[:id]) 49 | end 50 | 51 | def retrospective_params 52 | params.require(:retrospective).permit(:sprint_report, positive_points: [], negative_points: [] , improvements: []) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /app/helpers/burndown_helper.rb: -------------------------------------------------------------------------------- 1 | module BurndownHelper 2 | def check_pipeline(story, burned_stories) 3 | if story.pipeline == "Done" 4 | if burned_stories[story.final_date] == nil 5 | burned_stories[story.final_date] = story.story_points 6 | else 7 | burned_stories[story.final_date] += story.story_points 8 | end 9 | end 10 | end 11 | 12 | def get_burned_points(sprint, burned_stories) 13 | for story in sprint.stories 14 | check_pipeline(story, burned_stories) 15 | end 16 | 17 | return burned_stories 18 | end 19 | 20 | def get_total_points(sprint) 21 | total_points = 0 22 | 23 | for story in sprint.stories 24 | total_points += story.story_points 25 | end 26 | 27 | return total_points 28 | end 29 | 30 | def set_dates_and_points(burned_stories, date_axis, points_axis, range_dates, total_points) 31 | range_dates.each do |date| 32 | if burned_stories[date] == nil 33 | burned_stories[date] = total_points 34 | else 35 | total_points -= burned_stories[date] 36 | burned_stories[date] = total_points 37 | end 38 | date_axis.push(date) 39 | points_axis.push(burned_stories[date]) 40 | end 41 | end 42 | 43 | def set_ideal_line(days_of_sprint, ideal_line, planned_points) 44 | for day in (days_of_sprint).downto(0) 45 | ideal_line.push(planned_points * (day / (Float days_of_sprint))) 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /app/controllers/grades_controller.rb: -------------------------------------------------------------------------------- 1 | class GradesController < ApplicationController 2 | include MetricHelper 3 | include GradesDoc 4 | 5 | before_action :set_grade, only: [:show, :update, :destroy] 6 | 7 | before_action only: [:index, :create] do 8 | validate_project(0, :project_id) 9 | end 10 | 11 | before_action only: [:show, :update] do 12 | validate_grade(:id, 0) 13 | end 14 | 15 | def index 16 | grade = @project.grade 17 | render json: grade 18 | end 19 | 20 | def create 21 | if @project.grade.blank? 22 | grade = Grade.new(grade_params) 23 | grade.project = @project 24 | 25 | if grade.save 26 | render json: grade, status: :created 27 | else 28 | render json: grade.errors, status: :unprocessable_entity 29 | end 30 | else 31 | render json: @project.grade 32 | end 33 | end 34 | 35 | def update 36 | if @grade.update(grade_params) 37 | render json: @grade 38 | else 39 | render json: @grade.errors, status: :unprocessable_entity 40 | end 41 | end 42 | 43 | def show 44 | final_metric = get_metrics(@grade) 45 | render json: final_metric 46 | end 47 | 48 | def destroy 49 | @grade.destroy 50 | end 51 | 52 | private 53 | def grade_params 54 | params.require(:grade).permit(:weight_debts, :weight_velocity, :weight_burndown) 55 | end 56 | 57 | def set_grade 58 | @grade = Grade.find(params[:id]) 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /app/controllers/releases_controller.rb: -------------------------------------------------------------------------------- 1 | class ReleasesController < ApplicationController 2 | include ValidationsHelper 3 | include ReleasesDoc 4 | 5 | before_action :set_release, only: [:show, :update, :destroy] 6 | 7 | before_action only: [:index, :create] do 8 | validate_project(0, :project_id) 9 | end 10 | 11 | before_action only: [:show, :edit, :update, :destroy] do 12 | validate_release(:id, 0) 13 | end 14 | 15 | def index 16 | @releases = @project.releases.reverse 17 | render json: @releases 18 | end 19 | 20 | def show 21 | update_amount_of_sprints 22 | render json: @release 23 | end 24 | 25 | def create 26 | @release = Release.create(release_params) 27 | @release.project = @project 28 | update_amount_of_sprints 29 | if @release.save 30 | render json: @release, status: :created 31 | else 32 | render json: @release.errors, status: :unprocessable_entity 33 | end 34 | end 35 | 36 | def update 37 | if @release.update(release_params) 38 | render json: @release 39 | else 40 | render json: @release.errors, status: :unprocessable_entity 41 | end 42 | end 43 | 44 | def destroy 45 | @release.destroy 46 | end 47 | 48 | private 49 | def set_release 50 | @release = Release.find(params[:id]) 51 | end 52 | 53 | def release_params 54 | params.require(:release).permit(:name, :description, :amount_of_sprints, :initial_date, :final_date) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rails secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | # Shared secrets are available across all environments. 14 | 15 | # shared: 16 | # api_key: a1B2c3D4e5F6 17 | 18 | # Environmental secrets are only available for that specific environment. 19 | 20 | development: 21 | secret_key_base: fbaf4d96ad5701178ae81fc6158701d7117a0e212845183f90429489101c4e3dadce2a63ef87f228b2dd2aa7109dda79c73a9386bac2bcd85237d9984d68cf5c 22 | 23 | test: 24 | secret_key_base: 4b182d9b7c7d4c04e3229e2d0294952305f2c475cd73884316b3bf6230e95f00a53364702860ea59ecb6c88746ef52aa92043f0ab3bac84d4ecab1e1a8e0e0f4 25 | 26 | # Do not keep production secrets in the unencrypted secrets file. 27 | # Instead, either read values from the environment. 28 | # Or, use `bin/rails secrets:setup` to configure encrypted secrets 29 | # and move the `production:` environment over there. 30 | 31 | production: 32 | secret_key_base: e14c6cba62f0405feb13e55eed54d74c89c32d454a66b6a5babb6bb5f3fdca51776fff22cc589a4d3635c95f603332f69cd1b26ae56ad702cf69063350fdbd26 33 | -------------------------------------------------------------------------------- /app/helpers/issue_graphic_helper.rb: -------------------------------------------------------------------------------- 1 | module IssueGraphicHelper 2 | def get_issues_graphic(actual_date, option, issues) 3 | first_date = actual_date - 3.month 4 | number_of_issues = {} 5 | dates = [] 6 | total_open_issues = [0, 0, 0] 7 | total_closed_issues = [0, 0, 0] 8 | 9 | if option == "month" 10 | dates = [(actual_date - 2.month).strftime("%B"), (actual_date - 1.month).strftime("%B"), (actual_date).strftime("%B")] 11 | end 12 | 13 | 14 | issues.each do |issue| 15 | increase_issues(issue, actual_date, total_open_issues, total_closed_issues, first_date) 16 | end 17 | number_of_issues = { opened_issues: total_open_issues, closed_issues: total_closed_issues, months: dates } 18 | return number_of_issues 19 | end 20 | 21 | def increase_issues(issue, actual_date, total_open_issues, total_closed_issues, first_date) 22 | if issue.created_at.to_date <= actual_date && issue.created_at.to_date >= first_date 23 | interval_issues(issue, total_open_issues, actual_date) 24 | if issue.closed_at != nil 25 | interval_issues(issue, total_closed_issues, actual_date) 26 | end 27 | end 28 | end 29 | 30 | def interval_issues (issue, total_open_issues, actual_date) 31 | last_month = actual_date - 1.month 32 | if issue.created_at.to_date.month == actual_date.month 33 | increment_total(total_open_issues, 2) 34 | elsif issue.created_at.to_date.month == last_month.month 35 | increment_total(total_open_issues, 1) 36 | else 37 | increment_total(total_open_issues, 0) 38 | end 39 | end 40 | 41 | def increment_total(total, position) 42 | total[position] = total[position] + 1 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /app/helpers/velocity_helper.rb: -------------------------------------------------------------------------------- 1 | module VelocityHelper 2 | include BurndownHelper 3 | 4 | def get_total_points_release(release) 5 | total_points_release = 0 6 | 7 | for sprint in release.sprints 8 | total_points_release += get_total_points(sprint) 9 | end 10 | 11 | return total_points_release 12 | end 13 | 14 | def calculate_points(sprint) 15 | sprint_total_points = 0 16 | sprint_completed_points = 0 17 | 18 | sprint.stories.each do |story| 19 | sprint_total_points += story.story_points 20 | if story.pipeline == "Done" 21 | sprint_completed_points += story.story_points 22 | end 23 | end 24 | 25 | points = { sprint_total_points: sprint_total_points, sprint_completed_points: sprint_completed_points } 26 | end 27 | 28 | def get_sprints_informations(sprints, actual_sprint) 29 | names = [] 30 | total_points = [] 31 | completed_points = [] 32 | velocities = [] 33 | points = {} 34 | 35 | sprints.each do |sprint| 36 | if actual_sprint.final_date >= sprint.final_date 37 | names.push(sprint.name) 38 | 39 | points = calculate_points(sprint) 40 | 41 | total_points.push(points[:sprint_total_points]) 42 | completed_points.push(points[:sprint_completed_points]) 43 | velocities.push(calculate_velocity(completed_points)) 44 | end 45 | end 46 | 47 | sprints_informations = { total_points: total_points, completed_points: completed_points, names: names, velocities: velocities } 48 | end 49 | 50 | def calculate_velocity(completed_points) 51 | size = completed_points.size 52 | total_completed_points = 0 53 | 54 | completed_points.each do |point| 55 | total_completed_points += point 56 | end 57 | 58 | return Float(total_completed_points) / size 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | if Rails.root.join("tmp/caching-dev.txt").exist? 17 | config.action_controller.perform_caching = true 18 | 19 | config.cache_store = :memory_store 20 | config.public_file_server.headers = { 21 | "Cache-Control" => "public, max-age=#{2.days.seconds.to_i}" 22 | } 23 | else 24 | config.action_controller.perform_caching = false 25 | 26 | config.cache_store = :null_store 27 | end 28 | 29 | # Don't care if the mailer can't send. 30 | config.action_mailer.raise_delivery_errors = false 31 | 32 | config.action_mailer.perform_caching = false 33 | 34 | # Print deprecation notices to the Rails logger. 35 | config.active_support.deprecation = :log 36 | 37 | # Raise an error on page load if there are pending migrations. 38 | config.active_record.migration_error = :page_load 39 | 40 | 41 | # Raises error for missing translations 42 | # config.action_view.raise_on_missing_translations = true 43 | 44 | # Use an evented file watcher to asynchronously detect changes in source code, 45 | # routes, locales, etc. This feature depends on the listen gem. 46 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 47 | end 48 | -------------------------------------------------------------------------------- /test/models/grade_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class GradeTest < ActiveSupport::TestCase 4 | def setup 5 | @user = User.create( 6 | name: "Ronaldo", 7 | email: "Ronaldofenomeno@gmail.com", 8 | password: "123456789", 9 | password_confirmation: "123456789" 10 | ) 11 | 12 | @project = Project.create( 13 | name: "Falko", 14 | description: "This project is part of the course MDS.", 15 | is_project_from_github: true, 16 | user_id: @user.id, 17 | is_scoring: false 18 | ) 19 | 20 | @release = Release.create( 21 | name: "Release 1", 22 | description: "First Release.", 23 | initial_date: "01/01/2017", 24 | final_date: "02/02/2019", 25 | project_id: @project.id 26 | ) 27 | 28 | @sprint = Sprint.create( 29 | name: "Sprint1", 30 | description: "This sprint.", 31 | initial_date: "23-04-1993", 32 | final_date: "23-04-2003", 33 | release_id: @release.id 34 | ) 35 | 36 | @grade = Grade.create( 37 | weight_debts: "1", 38 | weight_burndown: "1", 39 | weight_velocity: "1", 40 | project_id: @project.id 41 | ) 42 | 43 | @grade = Grade.create( 44 | weight_debts: "1", 45 | weight_burndown: "1", 46 | weight_velocity: "1", 47 | project_id: @project.id 48 | ) 49 | end 50 | 51 | test "should save a grade" do 52 | assert @grade.save 53 | end 54 | 55 | test "Grade should have a weight debts" do 56 | @grade.weight_debts = nil 57 | assert_not @grade.save 58 | end 59 | 60 | test "Grade should have a weight burndown" do 61 | @grade.weight_burndown = nil 62 | assert_not @grade.save 63 | end 64 | 65 | test "Grade should have a weight velocity" do 66 | @grade.weight_velocity = nil 67 | assert_not @grade.save 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | 3 | apipie 4 | # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html 5 | post "authenticate", to: "authentication#authenticate" 6 | post "request_github_token", to: "users#request_github_token" 7 | post "remove_github_token", to: "users#remove_github_token" 8 | 9 | post "password/forgot", to: "passwords#forgot" 10 | post "password/reset", to: "passwords#reset" 11 | get "password/validate_token", to: "passwords#validate_token" 12 | 13 | 14 | get "sprints/:id/burndown", to: "sprints#get_burndown" 15 | 16 | get "repos", to: "projects#github_projects_list" 17 | get "projects/:id/contributors", to: "projects#get_contributors" 18 | post "projects/:id/issues/assignees", to: "issues#update_assignees" 19 | 20 | get "sprints/:id/to_do_stories", to: "stories#to_do_list" 21 | get "sprints/:id/done_stories", to: "stories#done_list" 22 | get "sprints/:id/doing_stories", to: "stories#doing_list" 23 | 24 | get "projects/:id/issues", to: "issues#index" 25 | get "projects/:id/issues/:page", to: "issues#index" 26 | post "projects/:id/issues", to: "issues#create" 27 | put "projects/:id/issues", to: "issues#update" 28 | patch "projects/:id/issues", to: "issues#update" 29 | delete "projects/:id/issues", to: "issues#close" 30 | 31 | get "sprints/:id/velocity", to: "sprints#get_velocity" 32 | 33 | post "projects/:id/reopen_issue", to: "issues#reopen_issue" 34 | post "/projects/:id/issues/graphic", to: "issues#issue_graphic_data" 35 | 36 | resources :users, shallow: true do 37 | resources :projects do 38 | resources :grades 39 | resources :releases do 40 | resources :sprints do 41 | resources :stories 42 | resources :revisions 43 | resources :retrospectives 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | "Cache-Control" => "public, max-age=#{1.hour.seconds.to_i}" 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | config.action_mailer.perform_caching = false 31 | 32 | # Tell Action Mailer not to deliver emails to the real world. 33 | # The :test delivery method accumulates sent emails in the 34 | # ActionMailer::Base.deliveries array. 35 | config.action_mailer.delivery_method = :test 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /test/models/revision_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class RevisionTest < ActiveSupport::TestCase 4 | def setup 5 | @user = User.create( 6 | name: "Ronaldo", 7 | email: "Ronaldofenomeno@gmail.com", 8 | password: "123456789", 9 | password_confirmation: "123456789" 10 | ) 11 | 12 | @project = Project.create( 13 | name: "Falko", 14 | description: "Esse projeto faz parte da disciplina MDS.", 15 | is_project_from_github: true, 16 | user_id: @user.id, 17 | is_scoring: false 18 | ) 19 | 20 | @release = Release.create( 21 | name: "Release 1", 22 | description: "First Release.", 23 | initial_date: "01/01/2017", 24 | final_date: "02/02/2019", 25 | project_id: @project.id 26 | ) 27 | 28 | @sprint = Sprint.create( 29 | name: "Sprint1", 30 | description: "Essa sprint", 31 | initial_date: "23-04-1993", 32 | final_date: "23-04-2003", 33 | release_id: @release.id 34 | ) 35 | 36 | @revision = Revision.create( 37 | done_report: ["A us11 foi feita com sucesso"], 38 | undone_report: ["Tudo foi feito"], 39 | sprint_id: @sprint.id 40 | ) 41 | end 42 | 43 | test "should save a revision" do 44 | assert @revision.save 45 | end 46 | 47 | test "Revision should have done_report between 0 and 500 caracters" do 48 | @revision.done_report = ["r" * 250] 49 | assert @revision.save 50 | end 51 | 52 | test "Revision should have undone_report between 0 and 500 caracters" do 53 | @revision.undone_report = ["r" * 250] 54 | assert @revision.save 55 | end 56 | 57 | test "Revision should have done_report less than 500 caracters" do 58 | @revision.done_report = "b" * 501 59 | assert_not @revision.save 60 | end 61 | 62 | test "Revision should have undone_report less than 500 caracters" do 63 | @revision.undone_report = "b" * 501 64 | assert_not @revision.save 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /app/docs/users_doc.rb: -------------------------------------------------------------------------------- 1 | module UsersDoc 2 | extend Apipie::DSL::Concern 3 | 4 | def_param_group :user do 5 | param :name, String, "User' name" 6 | param :email, String, "User's email" 7 | param :password_digest, String, "User's password" 8 | param :created_at, Date, "User's time of creation", allow_nil: false 9 | param :updated_at, Date, "User's time of edition", allow_nil: false 10 | param :description, String, "User's acess token" 11 | end 12 | 13 | api :GET, "/users/:id", "Show a user" 14 | error code: 401, desc: "Unauthorized" 15 | error code: 404, desc: "Not Found" 16 | error code: 500, desc: "Internal Server Error" 17 | description "Show a specific user" 18 | returns code: 200, desc: "Ok" do 19 | param_group :user 20 | end 21 | example <<-EOS 22 | { 23 | "id": 1, 24 | "name": "Vitor Barbosa", 25 | "email": "barbosa@gmail.com", 26 | "password_digest": "$2a$10$GDiOPE7a2YMzhaOdgW88NOecH3.eiBLcCZQjDjoi2ykrAdgreV2ge", 27 | "created_at": "2019-04-11T15:42:33.741Z", 28 | "updated_at": "2019-04-11T15:42:33.741Z", 29 | "access_token": null 30 | } 31 | EOS 32 | def show 33 | end 34 | 35 | api :POST, "/users", "Create a user" 36 | error code: 401, desc: "Unauthorized" 37 | error code: 404, desc: "Not Found" 38 | error code: 500, desc: "Internal Server Error" 39 | description "Create a specific user" 40 | param_group :user 41 | def create 42 | end 43 | 44 | api :PATCH, "/users/:id", "Update a user" 45 | error code: 401, desc: "Unauthorized" 46 | error code: 404, desc: "Not Found" 47 | error code: 500, desc: "Internal Server Error" 48 | description "Update a specific user" 49 | param_group :user 50 | def update 51 | end 52 | 53 | api :DELETE, "/users/:id", "Delete a user" 54 | error code: 401, desc: "Unauthorized" 55 | error code: 404, desc: "Not Found" 56 | error code: 500, desc: "Internal Server Error" 57 | description "Delete a specific user" 58 | param_group :user 59 | def destroy 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /app/controllers/passwords_controller.rb: -------------------------------------------------------------------------------- 1 | class PasswordsController < ApplicationController 2 | skip_before_action :authenticate_request 3 | 4 | def forgot 5 | if password_params[:email].blank? 6 | return render json: { error: "Email not present" }, status: :bad_request 7 | end 8 | 9 | user = User.find_by(email: password_params[:email].downcase) 10 | 11 | if user.present? 12 | user.generate_password_token! 13 | UserMailer.with(user: user).recover_password_email.deliver_now 14 | render json: { status: "ok" }, status: :ok 15 | else 16 | render json: { status: "ok" }, status: :ok 17 | end 18 | end 19 | 20 | def reset 21 | if password_params[:token].blank? 22 | return render json: { error: "Token not present" } , status: :bad_request 23 | end 24 | 25 | token = password_params[:token].to_s 26 | 27 | user = User.find_by(reset_password_token: token) 28 | 29 | if user.present? && user.password_token_valid? 30 | if user.reset_password!(password_params[:password]) 31 | render json: { status: "ok" }, status: :ok 32 | else 33 | render json: { error: user.errors.full_messages }, status: :unprocessable_entity 34 | end 35 | else 36 | render json: { error: ["Link not valid or expired. Try generating a new one."] }, status: :not_found 37 | end 38 | end 39 | 40 | def validate_token 41 | if password_params[:token].blank? 42 | return render json: { error: "Token not present" }, status: :bad_request 43 | else 44 | token = password_params[:token].to_s 45 | end 46 | 47 | user = User.find_by(reset_password_token: token) 48 | 49 | if user.present? && user.password_token_valid? 50 | render json: { status: "true" }, status: :ok 51 | else 52 | render json: { status: "false", error: ["Link not valid or expired. Try generating a new one."] }, status: :ok 53 | end 54 | end 55 | 56 | private 57 | 58 | def password_params 59 | params.permit(:email, :token, :password) 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/models/project_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ProjectTest < ActiveSupport::TestCase 4 | def setup 5 | @user = User.create( 6 | name: "Ronaldo", 7 | email: "Ronaldofenomeno@gmail.com", 8 | password: "123456789", 9 | password_confirmation: "123456789" 10 | ) 11 | 12 | @project = Project.create( 13 | name: "Falko", 14 | description: "Some project description.", 15 | user_id: @user.id, 16 | is_project_from_github: true, 17 | is_scoring: false 18 | ) 19 | 20 | @project2 = Project.create( 21 | name: "Falko", 22 | description: "Some project description.", 23 | user_id: @user.id, 24 | is_project_from_github: false 25 | ) 26 | end 27 | 28 | test "should save valid project" do 29 | assert @project.save 30 | end 31 | 32 | test "should not save project with a blank name" do 33 | @project.name = "" 34 | assert_not @project.save 35 | end 36 | 37 | test "should not save a project with name smaller than 2 characters" do 38 | @project.name = "M" 39 | assert_not @project.save 40 | end 41 | 42 | test "should not save a project with name bigger than 128 characters" do 43 | @project.name = "a" * 129 44 | assert_not @project.save 45 | end 46 | 47 | test "should save a project with an exacty 2 or 128 characters name" do 48 | @project.name = "Ma" 49 | assert @project.save 50 | @project.name = "a" * 128 51 | assert @project.save 52 | end 53 | 54 | test "should save project with a blank description" do 55 | @project.description = "" 56 | assert @project.save 57 | end 58 | 59 | test "should not save a project with description bigger than 256 characters" do 60 | @project.description = "a" * 257 61 | assert_not @project.save 62 | end 63 | 64 | test "should save a project with an exacty 256 characters description" do 65 | @project.description = "a" * 256 66 | assert @project.save 67 | end 68 | 69 | test "should save a project with a blank description" do 70 | @project.description = "" 71 | assert @project.save 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | ruby "2.4.1" 3 | 4 | git_source(:github) do |repo_name| 5 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") 6 | "https://github.com/#{repo_name}.git" 7 | end 8 | 9 | 10 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 11 | gem "rails", "~> 5.1.6" 12 | # Use postgresql as the database for Active Record 13 | gem "pg", "~> 1.0.0" 14 | # Use Puma as the app server 15 | gem "puma", "~> 3.7" 16 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 17 | # gem 'jbuilder', '~> 2.5' 18 | # Use ActiveModel has_secure_password 19 | gem "bcrypt", "~> 3.1.7" 20 | gem "jwt", "~> 1.5.6" 21 | gem "simple_command" 22 | gem "rest-client" 23 | 24 | gem "apipie-rails", "~> 0.5.11" 25 | 26 | gem "travis", "~> 1.8", ">= 1.8.8" 27 | 28 | gem "codeclimate-test-reporter", group: :test, require: nil 29 | 30 | gem "carrierwave", "~> 1.1" 31 | gem "carrierwave-base64", "~> 2.5", ">= 2.5.3" 32 | 33 | gem "simple_token_authentication", "~> 1.0" 34 | # Use Capistrano for deployment 35 | # gem 'capistrano-rails', group: :development 36 | 37 | # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible 38 | gem "rack-cors" 39 | gem "octokit", "~> 4.0" 40 | group :development, :test do 41 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 42 | gem "byebug", platforms: [:mri, :mingw, :x64_mingw] 43 | gem "minitest", "~> 5.8", ">= 5.8.4" 44 | gem "simplecov", require: false 45 | gem "minitest-reporters" 46 | end 47 | 48 | group :development do 49 | gem "listen", ">= 3.0.5", "< 3.2" 50 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 51 | gem "spring" 52 | gem "spring-watcher-listen", "~> 2.0.0" 53 | end 54 | 55 | group :test do 56 | gem "rubocop" 57 | end 58 | 59 | group :production do 60 | gem "rails_12factor" 61 | gem "activesupport", "~> 5.1", ">= 5.1.4" 62 | end 63 | 64 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 65 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby] 66 | -------------------------------------------------------------------------------- /test/models/sprint_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class SprintTest < ActiveSupport::TestCase 4 | def setup 5 | @user = User.create( 6 | name: "Gilberto", 7 | email: "gilbertin@teste.com", 8 | password: "1234567", 9 | password_confirmation: "1234567" 10 | ) 11 | 12 | @project = Project.create( 13 | name: "Falko", 14 | description: "Some project description.", 15 | is_project_from_github: true, 16 | user_id: @user.id, 17 | is_scoring: false 18 | ) 19 | 20 | @release = Release.create( 21 | name: "Release 1", 22 | description: "First Release.", 23 | initial_date: "01/01/2017", 24 | final_date: "02/02/2019", 25 | project_id: @project.id 26 | ) 27 | 28 | @sprint = Sprint.create( 29 | name: "Sprint 1", 30 | description: "Sprint 1 us10", 31 | initial_date: "06/10/2017", 32 | final_date: "13/10/2017", 33 | release_id: @release.id 34 | ) 35 | end 36 | 37 | test "should save a valid sprint" do 38 | assert @sprint.save 39 | end 40 | 41 | test "Sprint should have a name" do 42 | @sprint.name = "" 43 | assert_not @sprint.save 44 | end 45 | 46 | test "Sprint should have a start date" do 47 | @sprint.initial_date = "" 48 | assert_not @sprint.save 49 | end 50 | 51 | test "Sprint should have an end date" do 52 | @sprint.final_date = "" 53 | assert_not @sprint.save 54 | end 55 | 56 | test "Sprint name should have more than 1 characters" do 57 | @sprint.name = "s" 58 | assert_not @sprint.save 59 | end 60 | 61 | test "Sprint name should not have more than 128 characters" do 62 | @sprint.name = "a" * 129 63 | assert_not @sprint.save 64 | end 65 | 66 | test "Sprint name should have 2 characters" do 67 | @sprint.name = "ss" 68 | assert @sprint.save 69 | end 70 | 71 | test "Sprint name should have between 2 and 128 characters" do 72 | @sprint.name = "s" * 60 73 | assert @sprint.save 74 | end 75 | 76 | test "Sprint name should have 128 characters" do 77 | @sprint.name = "s" * 128 78 | assert @sprint.save 79 | end 80 | 81 | test "should not save a sprint with end date before start date" do 82 | @sprint.initial_date = "13/10/2017" 83 | @sprint.final_date = "06/10/2017" 84 | assert_not @sprint.save 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /app/docs/grades_doc.rb: -------------------------------------------------------------------------------- 1 | module GradesDoc 2 | extend Apipie::DSL::Concern 3 | 4 | def_param_group :grade do 5 | param :weight_burndown, Float, "Grade's weight of burndown" 6 | param :weight_velocity, Float, "Grade's weight of velocity" 7 | param :weight_debts, Float, "Grade's weight of debts" 8 | param :created_at, Date, "Grade's time of creation", allow_nil: false 9 | param :updated_at, Date, "Grade's time of edition", allow_nil: false 10 | param :project_id, :number, "Project's id" 11 | end 12 | 13 | api :GET, "/projects/:project_id/grades", "Show grades for a project" 14 | error code: 401, desc: "Unauthorized" 15 | error code: 404, desc: "Not Found" 16 | error code: 500, desc: "Internal Server Error" 17 | description "Show all projects of a specific user" 18 | returns code: 200, desc: "Ok" do 19 | param_group :grade 20 | end 21 | example <<-EOS 22 | { 23 | "id": 1, 24 | "weight_burndown": 1, 25 | "weight_velocity": 1, 26 | "weight_debts": 1, 27 | "created_at": "2019-04-27T19:20:21.581Z", 28 | "updated_at": "2019-04-27T19:20:21.581Z", 29 | "project_id": 1 30 | } 31 | EOS 32 | def index 33 | end 34 | 35 | api :POST, "/projects/:project_id/grades", "Create a new grade" 36 | error code: 401, desc: "Unauthorized" 37 | error code: 404, desc: "Not Found" 38 | error code: 500, desc: "Internal Server Error" 39 | description "Create grade for a specific project" 40 | param_group :grade 41 | def create 42 | end 43 | 44 | api :PATCH, "/grades/:id", "Update a grade" 45 | error code: 401, desc: "Unauthorized" 46 | error code: 404, desc: "Not Found" 47 | error code: 500, desc: "Internal Server Error" 48 | description "Update a created grade" 49 | param_group :grade 50 | def update 51 | end 52 | 53 | api :GET, "/grades/:id", "Show a grade" 54 | error code: 401, desc: "Unauthorized" 55 | error code: 404, desc: "Not Found" 56 | error code: 500, desc: "Internal Server Error" 57 | description "Show a specific grade" 58 | returns code: 200, desc: "Ok" do 59 | param_group :grade 60 | end 61 | def show 62 | end 63 | 64 | api :DELETE, "/grades/:id", "Delete a grade" 65 | error code: 401, desc: "Unauthorized" 66 | error code: 404, desc: "Not Found" 67 | error code: 500, desc: "Internal Server Error" 68 | description "Delete a specific grade" 69 | param_group :grade 70 | def destroy 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /app/adapters/adapter.rb: -------------------------------------------------------------------------------- 1 | module Adapter 2 | class GitHubProject 3 | def initialize(request) 4 | @logged_user = AuthorizeApiRequest.call(request.headers).result 5 | @client = Octokit::Client.new(access_token: @logged_user.access_token) 6 | Octokit.auto_paginate = true 7 | @client 8 | end 9 | 10 | def get_github_user 11 | user_login = @client.user.login 12 | return user_login 13 | end 14 | 15 | def get_github_repos(user_login) 16 | @client.repositories(user_login) 17 | end 18 | 19 | def get_github_orgs(user_login) 20 | @client.organizations(user_login) 21 | end 22 | 23 | def get_github_orgs_repos(org) 24 | @client.organization_repositories(org.login) 25 | end 26 | 27 | def get_contributors(github_slug) 28 | @client.contributors(github_slug) 29 | end 30 | end 31 | 32 | class GitHubIssue 33 | def initialize(request) 34 | @logged_user = AuthorizeApiRequest.call(request.headers).result 35 | @client = Octokit::Client.new(access_token: @logged_user.access_token) 36 | @client.auto_paginate = false 37 | @client 38 | end 39 | 40 | def get_github_user 41 | user_login = @client.user.login 42 | end 43 | 44 | def list_issues(github_slug, page_number) 45 | @client.list_issues(github_slug, page: page_number, per_page: 15) 46 | end 47 | 48 | def total_issues_pages(last_page) 49 | header_response = @client.last_response.rels[:last] 50 | unless header_response.nil? 51 | number_of_pages = header_response.href.match(/page=(\d+).*$/)[1] 52 | return number_of_pages.to_i 53 | else 54 | return last_page.to_i 55 | end 56 | end 57 | 58 | def list_all_issues(github_slug) 59 | @client.list_issues(github_slug, state: "all") 60 | end 61 | 62 | def create_issue(github_slug, issue_params) 63 | @client.create_issue(github_slug, issue_params[:name], issue_params[:body]) 64 | end 65 | 66 | def update_issue(github_slug, issue_params) 67 | @client.update_issue(github_slug, issue_params[:number], issue_params[:name], issue_params[:body]) 68 | end 69 | 70 | def close_issue(github_slug, issue_params) 71 | @client.close_issue(github_slug, issue_params[:number]) 72 | end 73 | 74 | def reopen_issue(github_slug, issue_params) 75 | @client.reopen_issue(github_slug, issue_params[:number]) 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "production" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. If you use this option 30 | # you need to make sure to reconnect any threads in the `on_worker_boot` 31 | # block. 32 | # 33 | # preload_app! 34 | 35 | # If you are preloading your application and using Active Record, it's 36 | # recommended that you close any connections to the database before workers 37 | # are forked to prevent connection leakage. 38 | # 39 | # before_fork do 40 | # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) 41 | # end 42 | 43 | # The code in the `on_worker_boot` will be called if you are using 44 | # clustered mode by specifying a number of `workers`. After each worker 45 | # process is booted, this block will be run. If you are using the `preload_app!` 46 | # option, you will want to use this block to reconnect to any threads 47 | # or connections that may have been created at application boot, as Ruby 48 | # cannot share connections between processes. 49 | # 50 | on_worker_boot do 51 | ActiveRecord::Base.establish_connection if defined?(ActiveRecord) 52 | end 53 | # 54 | 55 | # Allow puma to be restarted by `rails restart` command. 56 | plugin :tmp_restart 57 | -------------------------------------------------------------------------------- /test/models/release_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ReleaseTest < ActiveSupport::TestCase 4 | def setup 5 | @user = User.create( 6 | name: "Ronaldo", 7 | email: "Ronaldofenomeno@gmail.com", 8 | password: "123456789", 9 | password_confirmation: "123456789" 10 | ) 11 | 12 | @project = Project.create( 13 | name: "Falko", 14 | description: "Some project description.", 15 | user_id: @user.id, 16 | is_project_from_github: true, 17 | is_scoring: false 18 | ) 19 | 20 | @release = Release.create( 21 | name: "Release 1", 22 | description: "First Release.", 23 | initial_date: "01/01/2017", 24 | final_date: "02/02/2019", 25 | project_id: @project.id 26 | ) 27 | end 28 | 29 | test "should save a valid release" do 30 | assert @release.save 31 | end 32 | 33 | test "should not save a release without a project" do 34 | @release.project_id = nil 35 | assert_not @release.save 36 | end 37 | 38 | test "should not save a release without name" do 39 | @release.name = "" 40 | assert_not @release.save 41 | end 42 | 43 | test "should with name smaller than 2 characters" do 44 | @release.name = "R" 45 | assert_not @release.save 46 | end 47 | 48 | test "should with name greater than 80 characters" do 49 | @release.name = "R" * 81 50 | assert_not @release.save 51 | end 52 | 53 | test "should not save a release with initial date after final date" do 54 | @release.initial_date = "01/01/2017" 55 | @release.final_date = "01/01/2016" 56 | assert_not @release.save 57 | end 58 | 59 | test "should not save a release without initial date" do 60 | @release.initial_date = "" 61 | assert_not @release.save 62 | end 63 | 64 | test "should not save a release without final date" do 65 | @release.final_date = "" 66 | assert_not @release.save 67 | end 68 | 69 | test "should not save a release with an invalid initial date" do 70 | @release.initial_date = "50/50/2000" 71 | assert_not @release.save 72 | end 73 | 74 | test "should not save a release with an invalid final date" do 75 | @release.final_date = "50/50/2000" 76 | assert_not @release.save 77 | end 78 | 79 | test "should save a release with a blank description" do 80 | @release.description = "" 81 | assert @release.save 82 | end 83 | 84 | test "should not save a release with description greater than 256 characters" do 85 | @release.description = "A" * 257 86 | assert_not @release.save 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | require "rest-client" 2 | class UsersController < ApplicationController 3 | include ValidationsHelper 4 | include UsersDoc 5 | 6 | skip_before_action :authenticate_request, only: [:create, :all] 7 | 8 | before_action only: [:show, :update, :destroy] do 9 | set_user 10 | validate_user(:id, 0) 11 | end 12 | 13 | # GET /users/1 14 | def show 15 | @user = User.find(params[:id].to_i) 16 | render json: @user 17 | end 18 | 19 | # POST /users 20 | def create 21 | @user = User.new(user_params) 22 | if @user.save 23 | @token = AuthenticateUser.call(@user.email, @user.password) 24 | 25 | @result = { token: @token.result } 26 | 27 | response.set_header("auth_token", @token.result) 28 | render json: @result, status: :created 29 | else 30 | render json: @user.errors, status: :unprocessable_entity 31 | end 32 | end 33 | 34 | def request_github_token 35 | code_token = params[:code] 36 | 37 | result = RestClient.post( 38 | "https://github.com/login/oauth/access_token", 39 | client_id: ENV["CLIENT_ID"], 40 | client_secret: ENV["CLIENT_SECRET"], 41 | code: code_token, 42 | accept: :json 43 | ) 44 | 45 | 46 | access_token = result.split("&")[0].split("=")[1] 47 | 48 | unless access_token == "bad_verification_code" || access_token == nil 49 | @user = User.find(params[:id]) 50 | @user.access_token = access_token 51 | 52 | if @user.update_column(:access_token, access_token) 53 | render json: @user 54 | else 55 | render json: @user.errors, status: :unprocessable_entity 56 | end 57 | else 58 | render json: result, status: :bad_request 59 | end 60 | end 61 | 62 | def remove_github_token 63 | @user = User.find(params[:id]) 64 | @user.access_token = nil 65 | if @user.update_column(:access_token, nil) 66 | render json: @user 67 | else 68 | render json: @user.errors, status: :unprocessable_entity 69 | end 70 | end 71 | 72 | # PATCH/PUT /users/1 73 | def update 74 | if @user.update(user_params) 75 | render json: @user 76 | else 77 | render json: @user.errors, status: :unprocessable_entity 78 | end 79 | end 80 | 81 | # DELETE /users/1 82 | def destroy 83 | @user.destroy 84 | render json: { status: 200, message: "User deleted successfully" }.to_json 85 | end 86 | 87 | private 88 | def set_user 89 | @user = User.find(params[:id]) 90 | end 91 | 92 | def user_params 93 | params.require(:user).permit(:name, :email, :password, :password_confirmation) 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /test/models/retrospective_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class RetrospectiveTest < ActiveSupport::TestCase 4 | def setup 5 | @user = User.create( 6 | name: "Ronaldo", 7 | email: "ronaldofenomeno@gmail.com", 8 | password: "123456789", 9 | password_confirmation: "123456789" 10 | ) 11 | 12 | @project = Project.create( 13 | name: "Falko", 14 | description: "Some project description.", 15 | user_id: @user.id, 16 | is_project_from_github: true, 17 | is_scoring: false 18 | ) 19 | 20 | @release = Release.create( 21 | name: "Release 1", 22 | description: "First Release.", 23 | initial_date: "01/01/2017", 24 | final_date: "02/02/2019", 25 | project_id: @project.id 26 | ) 27 | 28 | @sprint = Sprint.create( 29 | name: "Sprint1", 30 | description: "Some sprint", 31 | initial_date: "23/04/1993", 32 | final_date: "23/04/2003", 33 | release_id: @release.id 34 | ) 35 | 36 | @retrospective = Retrospective.create( 37 | sprint_report: "Sprint description", 38 | positive_points: ["Very good"], 39 | negative_points: ["No tests"], 40 | improvements: ["Improve front-end"], 41 | sprint_id: @sprint.id 42 | ) 43 | end 44 | 45 | test "should save a retrospective" do 46 | assert @retrospective.save 47 | end 48 | 49 | test "Restrospective should have sprint_report less than 1500 characters" do 50 | @retrospective.sprint_report = "a" * 1501 51 | assert_not @retrospective.save 52 | end 53 | 54 | test "Restrospective should have sprint_report 600 characters" do 55 | @retrospective.sprint_report = "a" * 600 56 | assert @retrospective.save 57 | end 58 | 59 | test "Restrospective should have positive_points less than 500 characters" do 60 | @retrospective.positive_points = "b" * 501 61 | assert_not @retrospective.save 62 | end 63 | 64 | test "Restrospective should have positive_points" do 65 | @retrospective.positive_points = "" 66 | assert_not @retrospective.save 67 | end 68 | 69 | test "Restrospective should have negative_points less than 500 characters" do 70 | @retrospective.negative_points = "c" * 501 71 | assert_not @retrospective.save 72 | end 73 | 74 | test "Restrospective should have negative_points" do 75 | @retrospective.negative_points = "" 76 | assert_not @retrospective.save 77 | end 78 | 79 | test "Restrospective should have improvements less than 500 characters" do 80 | @retrospective.improvements = "d" * 501 81 | assert_not @retrospective.save 82 | end 83 | 84 | test "Restrospective should have improvements" do 85 | @retrospective.improvements = "" 86 | assert_not @retrospective.save 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /app/docs/revisions_doc.rb: -------------------------------------------------------------------------------- 1 | module RevisionsDoc 2 | extend Apipie::DSL::Concern 3 | 4 | def_param_group :revision do 5 | param :done_report, Array, of: String, desc: "Sprint revision's done report" 6 | param :undone_report, Array, of: String, desc: "Sprint revision's undone report" 7 | param :created_at, Date, "Revision's time of creation", allow_nil: false 8 | param :updated_at, Date, "Revision's time of edition", allow_nil: false 9 | param :sprint_id, :number, "Id of sprint revised" 10 | end 11 | 12 | api :GET, "/sprints/:sprint_id/revisions", "Show revisions for a sprint" 13 | error code: 401, desc: "Unauthorized" 14 | error code: 404, desc: "Not Found" 15 | error code: 500, desc: "Internal Server Error" 16 | description "Show all revisions of a specific sprint" 17 | returns code: 200, desc: "Ok" do 18 | param_group :revision 19 | end 20 | example <<-EOS 21 | { 22 | "id": 1, 23 | "done_report": [ 24 | "Story US11 was done." 25 | ], 26 | "undone_report": [ 27 | "Story US21 was not done." 28 | ], 29 | "created_at": "2019-04-11T15:42:35.826Z", 30 | "updated_at": "2019-04-11T15:42:35.826Z", 31 | "sprint_id": 1 32 | } 33 | EOS 34 | def index 35 | end 36 | 37 | api :POST, "/sprints/:sprint_id/revisions", "Create a revision" 38 | error code: 401, desc: "Unauthorized" 39 | error code: 404, desc: "Not Found" 40 | error code: 500, desc: "Internal Server Error" 41 | description "Create revision for a specific sprint" 42 | param_group :revision 43 | def create 44 | end 45 | 46 | api :GET, "/revisions/:id", "Show a revision" 47 | error code: 401, desc: "Unauthorized" 48 | error code: 404, desc: "Not Found" 49 | error code: 500, desc: "Internal Server Error" 50 | description "Show a revision of a specific sprint" 51 | returns code: 200, desc: "Ok" do 52 | param_group :revision 53 | end 54 | example <<-EOS 55 | { 56 | "id": 2, 57 | "done_report": [ 58 | "Story US12 was done." 59 | ], 60 | "undone_report": [ 61 | "Story US22 was not done." 62 | ], 63 | "created_at": "2019-04-11T15:42:35.851Z", 64 | "updated_at": "2019-04-11T15:42:35.851Z", 65 | "sprint_id": 2 66 | } 67 | EOS 68 | def show 69 | end 70 | 71 | api :PATCH, "/revisions/:id", "Update a revision" 72 | error code: 401, desc: "Unauthorized" 73 | error code: 404, desc: "Not Found" 74 | error code: 500, desc: "Internal Server Error" 75 | description "Update revision for a specific sprint" 76 | param_group :revision 77 | def update 78 | end 79 | 80 | api :DELETE, "/revisions/:id", "Delete a revision" 81 | error code: 401, desc: "Unauthorized" 82 | error code: 404, desc: "Not Found" 83 | error code: 500, desc: "Internal Server Error" 84 | description "Delete revision for a specific sprint" 85 | param_group :revision 86 | def destroy 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | @echo "*********************************\n" 3 | @echo "Lifting up the whole environment!\n" 4 | @echo "*********************************\n" 5 | @docker-compose up 6 | 7 | quiet-run: 8 | @echo "*********************************\n" 9 | @echo "Lifting up the whole environment!\n" 10 | @echo "*********************************\n" 11 | @docker-compose up --detach 12 | 13 | run-db: 14 | @echo "************************\n" 15 | @echo "Lifting up the database!\n" 16 | @echo "************************\n" 17 | @docker-compose up falko-database 18 | 19 | run-api: 20 | @echo "*************************\n" 21 | @echo "Lifting up the api!\n" 22 | @echo "*************************\n" 23 | @docker-compose up falko-server 24 | 25 | console: 26 | @echo "*************************\n" 27 | @echo "Entering the rails console!\n" 28 | @echo "*************************\n" 29 | @docker-compose falko-server rails console 30 | 31 | create-db: 32 | @echo "*************************\n" 33 | @echo "Creating rails database!\n" 34 | @echo "*************************\n" 35 | @docker-compose exec falko-server bundle exec rails db:create 36 | 37 | seed: 38 | @echo "*************************\n" 39 | @echo "Executing database seed scripts\n" 40 | @echo "*************************\n" 41 | @docker-compose exec falko-server bundle exec rails db:seed 42 | 43 | down: 44 | @echo "******************************\n" 45 | @echo "Dropping down the environment!\n" 46 | @echo "******************************\n" 47 | @docker-compose down 48 | 49 | ps: 50 | @echo "************************\n" 51 | @echo "Listing running services\n" 52 | @echo "************************\n" 53 | @docker-compose ps 54 | 55 | migrate: 56 | @echo "******************\n" 57 | @echo "Migrating database\n" 58 | @echo "******************\n" 59 | @docker-compose exec falko-server bundle exec rails db:migrate 60 | 61 | test: 62 | @echo "***************************\n" 63 | @echo "Executing all api tests\n" 64 | @echo "***************************\n" 65 | @docker-compose exec falko-server bundle exec rails test 66 | 67 | rm-network: 68 | @echo "**********************************\n" 69 | @echo "Removing all networks!\n" 70 | @echo "**********************************\n" 71 | @docker network rm $(sudo docker network ls -q) 72 | 73 | rm-flk-network: 74 | @echo "**********************************\n" 75 | @echo "Removing Falko network!\n" 76 | @echo "**********************************\n" 77 | @docker network rm falko-backend 78 | 79 | rm-volume: 80 | @echo "*********************\n" 81 | @echo "Removing all volumes!\n" 82 | @echo "*********************\n" 83 | @docker volume rm $(sudo docker volume ls -q) 84 | 85 | .PHONY: no_targets__ list 86 | no_targets__: 87 | list: 88 | @sh -c "$(MAKE) -p no_targets__ -prRn -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -F':' '/^[a-zA-Z0-9][^\$$#\/\\t=]*:([^=]|$$)/ {split(\$$1,A,/ /);for(i in A)print A[i]}' | grep -v '__\$$' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$'" 89 | -------------------------------------------------------------------------------- /app/controllers/projects_controller.rb: -------------------------------------------------------------------------------- 1 | require "rest-client" 2 | 3 | class ProjectsController < ApplicationController 4 | include ValidationsHelper 5 | include ProjectsDoc 6 | 7 | before_action :set_project, only: [:destroy, :show, :get_contributors] 8 | 9 | before_action only: [:index, :create] do 10 | validate_user(0, :user_id) 11 | end 12 | 13 | before_action only: [:show, :update, :destroy] do 14 | validate_project(:id, 0) 15 | end 16 | 17 | def index 18 | @projects = User.find(params[:user_id]).projects 19 | render json: @projects 20 | end 21 | 22 | def github_projects_list 23 | client = Adapter::GitHubProject.new(request) 24 | 25 | user_login = client.get_github_user 26 | 27 | @form_params_user = { user: [] } 28 | @form_params_user[:user].push(login: user_login) 29 | 30 | user_repos = [] 31 | 32 | (client.get_github_repos(user_login)).each do |repo| 33 | user_repos.push(repo.name) 34 | end 35 | 36 | @form_params_user[:user].push(repos: user_repos) 37 | 38 | @form_params_orgs = { orgs: [] } 39 | 40 | (client.get_github_orgs(user_login)).each do |org| 41 | repos_names = [] 42 | (client.get_github_orgs_repos(org)).each do |repo| 43 | repos_names.push(repo.name) 44 | end 45 | @form_params_orgs[:orgs].push(name: org.login, repos: repos_names) 46 | end 47 | 48 | @form_params_user_orgs = @form_params_orgs.merge(@form_params_user) 49 | 50 | render json: @form_params_user_orgs 51 | end 52 | 53 | def show 54 | render json: @project 55 | end 56 | 57 | def create 58 | @project = Project.create(project_params) 59 | @project.user_id = @current_user.id 60 | 61 | if @project.save 62 | render json: @project, status: :created 63 | else 64 | render json: @project.errors, status: :unprocessable_entity 65 | end 66 | end 67 | 68 | def update 69 | if @project.update(project_params) 70 | render json: @project 71 | else 72 | render json: @project.errors, status: :unprocessable_entity 73 | end 74 | end 75 | 76 | def destroy 77 | @project.destroy 78 | end 79 | 80 | def get_contributors 81 | client = Adapter::GitHubProject.new(request) 82 | 83 | contributors = [] 84 | 85 | (client.get_contributors(@project.github_slug)).each do |contributor| 86 | contributors.push(contributor.login) 87 | end 88 | 89 | render json: contributors, status: :ok 90 | end 91 | 92 | private 93 | def set_project 94 | begin 95 | @project = Project.find(params[:id]) 96 | rescue ActiveRecord::RecordNotFound 97 | render json: { errors: "Project not found" }, status: :not_found 98 | end 99 | end 100 | 101 | def project_params 102 | params.require(:project).permit(:name, :description, :user_id, :is_project_from_github, :github_slug, :is_scoring) 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | def setup 5 | @user = User.create( 6 | name: "Gilberto", 7 | email: "gilbertin@teste.com", 8 | password: "1234567", 9 | password_confirmation: "1234567" 10 | ) 11 | end 12 | 13 | test "should save a valid user" do 14 | assert @user.save 15 | end 16 | 17 | test "User should have a name" do 18 | @user.name = "" 19 | assert_not @user.save 20 | end 21 | 22 | test "User name should have more than 2 characters" do 23 | @user.name = "gi" 24 | assert_not @user.save 25 | end 26 | 27 | test "User name should not have more than 80 characters" do 28 | @user.name = "a" * 81 29 | assert_not @user.save 30 | end 31 | 32 | test "The number of characters in user name is between 3 and 80" do 33 | @user.name = "oda" 34 | assert @user.save 35 | @user.name = "a" * 80 36 | assert @user.save 37 | end 38 | 39 | test "User should have a email" do 40 | @user.email = "" 41 | assert_not @user.save 42 | end 43 | 44 | test "should note save user with duplicate emails" do 45 | duplicate_user = @user.dup 46 | duplicate_user.email = @user.email 47 | @user.save 48 | assert_not duplicate_user.save 49 | end 50 | 51 | test "should save email with right validation format" do 52 | valid_addresses = %w[testing@example.com user@foo.COM A_MEM-BER@foo.bar.org 53 | first.last@foo.jp alice+bob@baz.cn] 54 | valid_addresses.each do |valid_address| 55 | @user.email = valid_address 56 | assert @user.save, "#{valid_address.inspect} should be valid" 57 | end 58 | end 59 | 60 | test "should not save user with email in an invalid format" do 61 | invalid_addresses = %w[testing@example,com user_at_foo.org user.name@example. 62 | foo@bar_baz.com foo@bar+baz.com] 63 | invalid_addresses.each do |invalid_address| 64 | @user.email = invalid_address 65 | assert_not @user.save, "#{invalid_address.inspect} should be invalid" 66 | end 67 | end 68 | 69 | test "User should not have a blank password" do 70 | @user_wrong = User.create( 71 | name: "Gilberto", 72 | email: "gilbertin@teste.com", 73 | password: "", 74 | password_confirmation: "", 75 | ) 76 | 77 | assert_not @user_wrong.save 78 | end 79 | 80 | test "User password should not have less than 6 characters" do 81 | @user_wrong = User.create( 82 | name: "Gilberto", 83 | email: "gilbertin@teste.com", 84 | password: "g", 85 | password_confirmation: "g", 86 | ) 87 | 88 | assert_not @user_wrong.save 89 | end 90 | 91 | test "User password should not have more than 80 characters" do 92 | @user_wrong = User.create( 93 | name: "Gilberto", 94 | email: "gilbertin@teste.com", 95 | password: "g" * 81, 96 | password_confirmation: "g" * 81, 97 | ) 98 | 99 | assert_not @user_wrong.save 100 | end 101 | 102 | test "The number of characters in user password is between 6 and 80" do 103 | assert @user.save 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fritzo@uber.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /app/docs/retrospectives_doc.rb: -------------------------------------------------------------------------------- 1 | module RetrospectivesDoc 2 | extend Apipie::DSL::Concern 3 | 4 | def_param_group :retrospective do 5 | param :sprint_report, String, "Sprint reports" 6 | param :positive_points, Array, of: String, desc: "Sprint positive points", default_value: [] 7 | param :negative_points, Array, of: String, desc: "Sprint negative points", default_value: [] 8 | param :improvements, Array, of: String, desc: "Sprint improvements", default_value: [] 9 | param :created_at, Date, "Sprint's time of creation", allow_nil: false 10 | param :updated_at, Date, "Sprint's time of edition", allow_nil: false 11 | param :sprint_id, :number, "Sprint's id" 12 | end 13 | 14 | api :GET, "/sprints/:sprint_id/retrospectives", "Show retrospectives for a sprint" 15 | error code: 401, desc: "Unauthorized" 16 | error code: 404, desc: "Not Found" 17 | error code: 500, desc: "Internal Server Error" 18 | description "Show all retrospectives of a specific sprint" 19 | returns code: 200, desc: "Ok" do 20 | param_group :retrospective 21 | end 22 | example <<-EOS 23 | { 24 | "id": 1, 25 | "sprint_report": "Sprint 1", 26 | "positive_points": [ 27 | "Very good" 28 | ], 29 | "negative_points": [ 30 | "No tests" 31 | ], 32 | "improvements": [ 33 | "Improve front-end" 34 | ], 35 | "created_at": "2019-04-11T15:42:36.102Z", 36 | "updated_at": "2019-04-11T15:42:36.102Z", 37 | "sprint_id": 1 38 | } 39 | EOS 40 | def index 41 | end 42 | 43 | api :POST, "/sprints/:sprint_id/retrospectives", "Create a retrospective" 44 | error code: 401, desc: "Unauthorized" 45 | error code: 404, desc: "Not Found" 46 | error code: 500, desc: "Internal Server Error" 47 | description "Create retrospective of a specific sprint" 48 | param_group :retrospective 49 | def create 50 | end 51 | 52 | api :GET, "/retrospectives/:id", "Show a retrospective" 53 | error code: 401, desc: "Unauthorized" 54 | error code: 404, desc: "Not Found" 55 | error code: 500, desc: "Internal Server Error" 56 | description "Show a specific retrospective of a sprint" 57 | returns code: 200, desc: "Ok" do 58 | param_group :retrospective 59 | end 60 | example <<-EOS 61 | { 62 | "id": 1, 63 | "sprint_report": "Sprint 1", 64 | "positive_points": [ 65 | "Very good" 66 | ], 67 | "negative_points": [ 68 | "No tests" 69 | ], 70 | "improvements": [ 71 | "Improve front-end" 72 | ], 73 | "created_at": "2019-04-11T15:42:36.102Z", 74 | "updated_at": "2019-04-11T15:42:36.102Z", 75 | "sprint_id": 1 76 | } 77 | EOS 78 | def show 79 | end 80 | 81 | api :PATCH, "/retrospectives/:id", "Update a retrospective" 82 | error code: 401, desc: "Unauthorized" 83 | error code: 404, desc: "Not Found" 84 | error code: 500, desc: "Internal Server Error" 85 | description "Update retrospective of a specific sprint" 86 | param_group :retrospective 87 | def update 88 | end 89 | 90 | api :DELETE, "/retrospectives/:id", "Delete a retrospective" 91 | error code: 401, desc: "Unauthorized" 92 | error code: 404, desc: "Not Found" 93 | error code: 500, desc: "Internal Server Error" 94 | description "Delete retrospective of a specific sprint" 95 | param_group :retrospective 96 | def destroy 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /test/models/story_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class StoryTest < ActiveSupport::TestCase 4 | def setup 5 | @user = User.create( 6 | name: "Gilberto", 7 | email: "gilbertin@teste.com", 8 | password: "1234567", 9 | password_confirmation: "1234567" 10 | ) 11 | 12 | @project = Project.create( 13 | name: "Falko", 14 | description: "Some project description.", 15 | user_id: @user.id, 16 | is_project_from_github: true, 17 | is_scoring: false 18 | ) 19 | 20 | @release = Release.create( 21 | name: "Release 1", 22 | description: "First Release.", 23 | initial_date: "01/01/2017", 24 | final_date: "02/02/2019", 25 | project_id: @project.id 26 | ) 27 | 28 | @sprint = Sprint.create( 29 | name: "Sprint 1", 30 | description: "Sprint 1 us10", 31 | initial_date: "06/10/2017", 32 | final_date: "13/10/2017", 33 | release_id: @release.id 34 | ) 35 | 36 | @story = Story.create( 37 | name: "Story 1", 38 | description: "Story 1 us14", 39 | assign: "Lucas", 40 | pipeline: "in progress", 41 | initial_date: "01/01/2017", 42 | issue_number: "10", 43 | sprint_id: @sprint.id 44 | ) 45 | end 46 | 47 | test "should save a valid story" do 48 | assert @story.save 49 | end 50 | 51 | test "Story should have a name" do 52 | @story.name = "" 53 | assert_not @story.save 54 | end 55 | 56 | test "Story should have a start date" do 57 | @story.initial_date = "" 58 | assert_not @story.save 59 | end 60 | 61 | test "Story name should have more than 1 characters" do 62 | @story.name = "s" 63 | assert_not @story.save 64 | end 65 | 66 | test "Story name should not have more than 128 characters" do 67 | @story.name = "a" * 129 68 | assert_not @story.save 69 | end 70 | 71 | test "The number of characters in story name is between 2 and 128" do 72 | @story.name = "ss" 73 | assert @story.save 74 | 75 | @story.name = "s" * 60 76 | assert @story.save 77 | 78 | @story.name = "s" * 128 79 | assert @story.save 80 | end 81 | 82 | test "Story assign should have more than 1 characters" do 83 | @story.assign = "s" 84 | assert_not @story.save 85 | end 86 | 87 | test "Story name should not have more than 32 characters" do 88 | @story.assign = "a" * 33 89 | assert_not @story.save 90 | end 91 | 92 | test "The number of characters in story assign is between 2 and 32" do 93 | @story.assign = "ss" 94 | assert @story.save 95 | 96 | @story.assign = "s" * 16 97 | assert @story.save 98 | 99 | @story.assign = "s" * 32 100 | assert @story.save 101 | end 102 | 103 | test "Story pipeline should have more than 3 characters" do 104 | @story.pipeline = "s" 105 | assert_not @story.save 106 | end 107 | 108 | test "Story name should not have more than 16 characters" do 109 | @story.pipeline = "a" * 17 110 | assert_not @story.save 111 | end 112 | 113 | test "The number of characters in story pipeline is between 4 and 16" do 114 | @story.pipeline = "ssss" 115 | assert @story.save 116 | 117 | @story.pipeline = "s" * 5 118 | assert @story.save 119 | 120 | @story.pipeline = "s" * 16 121 | assert @story.save 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /app/controllers/stories_controller.rb: -------------------------------------------------------------------------------- 1 | class StoriesController < ApplicationController 2 | include ValidationsHelper 3 | include StoriesDoc 4 | 5 | before_action :set_story, only: [:show, :update, :destroy] 6 | 7 | before_action only: [:index, :create] do 8 | validate_sprint(0, :sprint_id) 9 | end 10 | 11 | 12 | before_action only: [:show, :edit, :update, :destroy] do 13 | validate_sprint_dependencies(:id, "story") 14 | end 15 | 16 | def index 17 | # @sprint used from validate_sprint(0, :sprint_id) 18 | @stories = @sprint.stories.reverse 19 | render json: @stories 20 | end 21 | 22 | def to_do_list 23 | @sprint = Sprint.find(params[:id]) 24 | 25 | @stories = @sprint.stories.select { |story| story.pipeline == "To Do" } 26 | 27 | render json: format_json_output(@stories) 28 | end 29 | 30 | def doing_list 31 | @sprint = Sprint.find(params[:id]) 32 | 33 | @stories = @sprint.stories.select { |story| story.pipeline == "Doing" } 34 | 35 | render json: format_json_output(@stories) 36 | end 37 | 38 | def done_list 39 | @sprint = Sprint.find(params[:id]) 40 | 41 | @stories = @sprint.stories.select { |story| story.pipeline == "Done" } 42 | 43 | render json: format_json_output(@stories) 44 | end 45 | 46 | def show 47 | @story = Story.find(params[:id]) 48 | render json: @story 49 | end 50 | 51 | def create 52 | @story = Story.create(story_params) 53 | @story.sprint = @sprint 54 | if validate_stories(@story.story_points, 0, :sprint_id) 55 | if @story.save 56 | render json: @story, status: :created 57 | else 58 | render json: @story.errors, status: :unprocessable_entity 59 | end 60 | else 61 | render json: { error: "Story points have to be set" }, status: :unprocessable_entity 62 | end 63 | end 64 | 65 | def update 66 | if story_params[:pipeline] == "Done" 67 | @story.final_date = Date.today 68 | end 69 | if @story.update(story_params) 70 | render json: @story 71 | else 72 | render json: @story.errors, status: :unprocessable_entity 73 | end 74 | end 75 | 76 | def destroy 77 | @story.destroy 78 | end 79 | 80 | private 81 | def set_story 82 | @story = Story.find(params[:id]) 83 | end 84 | 85 | def format_json_output(stories) 86 | form_params = { stories_infos: [] } 87 | 88 | if stories != nil 89 | if stories.kind_of?(Array) 90 | stories.each do |story| 91 | form_params[:stories_infos].push(name: story.name, description: story.description, assign: story.assign, pipeline: story.pipeline, initial_date: story.initial_date, story_points: story.story_points, final_date: story.final_date, issue_number: story.issue_number, id: story.id) 92 | end 93 | else 94 | form_params[:stories_infos].push(name: stories.name, description: stories.description, assign: stories.assign, pipeline: stories.pipeline, initial_date: stories.initial_date, story_points: stories.story_points, final_date: stories.final_date, issue_number: stories.issue_number, id: stories.id) 95 | end 96 | end 97 | 98 | form_params 99 | end 100 | 101 | def story_params 102 | params.require(:story).permit(:name, :description, :assign, :pipeline, :initial_date, :story_points, :final_date, :issue_number, :issue_id) 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /app/docs/releases_doc.rb: -------------------------------------------------------------------------------- 1 | module ReleasesDoc 2 | extend Apipie::DSL::Concern 3 | 4 | def_param_group :release do 5 | param :name, String, "Release's name" 6 | param :description, String, "Release's description" 7 | param :amount_of_sprints, Integer, "Release's number of sprints" 8 | param :created_at, Date, "Release's time of creation", allow_nil: false 9 | param :updated_at, Date, "Release's time of edition", allow_nil: false 10 | param :project_id, :number, "Project's id" 11 | param :initial_date, Date, "Release's initial date" 12 | param :final_date, Date, "Release's final date" 13 | end 14 | 15 | api :GET, "/projects/:project_id/releases", "Show releases for a project" 16 | error code: 401, desc: "Unauthorized" 17 | error code: 404, desc: "Not Found" 18 | error code: 500, desc: "Internal Server Error" 19 | description "Show all releases of a specific project" 20 | returns code: 200, desc: "Ok" do 21 | param_group :release 22 | end 23 | example <<-EOS 24 | [ 25 | { 26 | "id": 2, 27 | "name": "R2", 28 | "description": "Agile Release", 29 | "amount_of_sprints": 0, 30 | "created_at": "2019-04-11T15:42:34.118Z", 31 | "updated_at": "2019-04-27T20:55:28.228Z", 32 | "project_id": 1, 33 | "initial_date": "2016-01-01", 34 | "final_date": "2016-12-01" 35 | }, 36 | { 37 | "id": 1, 38 | "name": "R1", 39 | "description": "RUP Release", 40 | "amount_of_sprints": 2, 41 | "created_at": "2019-04-11T15:42:34.101Z", 42 | "updated_at": "2019-04-27T20:55:28.231Z", 43 | "project_id": 1, 44 | "initial_date": "2016-01-01", 45 | "final_date": "2016-10-01" 46 | } 47 | ] 48 | EOS 49 | def index 50 | end 51 | 52 | api :GET, "releases/:id", "Show a release for a project" 53 | error code: 401, desc: "Unauthorized" 54 | error code: 404, desc: "Not Found" 55 | error code: 500, desc: "Internal Server Error" 56 | description "Show a specific release of a project" 57 | returns code: 200, desc: "Ok" do 58 | param_group :release 59 | end 60 | example <<-EOS 61 | { 62 | "project_id": 1, 63 | "id": 1, 64 | "amount_of_sprints": 2, 65 | "name": "R1", 66 | "description": "RUP Release", 67 | "initial_date": "2016-01-01", 68 | "final_date": "2016-10-01", 69 | "created_at": "2019-04-11T15:42:34.101Z", 70 | "updated_at": "2019-04-27T20:55:28.231Z" 71 | } 72 | EOS 73 | def show 74 | end 75 | 76 | api :POST, "/projects/:project_id/releases", "Create a release" 77 | error code: 401, desc: "Unauthorized" 78 | error code: 404, desc: "Not Found" 79 | error code: 500, desc: "Internal Server Error" 80 | description "Create release of a project" 81 | param_group :release 82 | def create 83 | end 84 | 85 | api :PATCH, "/releases/:id", "Update a release" 86 | error code: 401, desc: "Unauthorized" 87 | error code: 404, desc: "Not Found" 88 | error code: 500, desc: "Internal Server Error" 89 | description "Update release of a project" 90 | param_group :release 91 | def update 92 | end 93 | 94 | api :DELETE, "/releases/:id", "Delete a release" 95 | error code: 401, desc: "Unauthorized" 96 | error code: 404, desc: "Not Found" 97 | error code: 500, desc: "Internal Server Error" 98 | description "Delete release of a project" 99 | param_group :release 100 | def destroy 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Attempt to read encrypted secrets from `config/secrets.yml.enc`. 18 | # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or 19 | # `config/secrets.yml.key`. 20 | config.read_encrypted_secrets = true 21 | 22 | # Disable serving static files from the `/public` folder by default since 23 | # Apache or NGINX already handles this. 24 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? 25 | 26 | 27 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 28 | # config.action_controller.asset_host = 'http://assets.example.com' 29 | 30 | # Specifies the header that your server uses for sending files. 31 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 32 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 33 | 34 | 35 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 36 | # config.force_ssl = true 37 | 38 | # Use the lowest log level to ensure availability of diagnostic information 39 | # when problems arise. 40 | config.log_level = :debug 41 | 42 | # Prepend all log lines with the following tags. 43 | config.log_tags = [ :request_id ] 44 | 45 | # Use a different cache store in production. 46 | # config.cache_store = :mem_cache_store 47 | 48 | # Use a real queuing backend for Active Job (and separate queues per environment) 49 | # config.active_job.queue_adapter = :resque 50 | # config.active_job.queue_name_prefix = "Falko-2017_2_#{Rails.env}" 51 | config.action_mailer.perform_caching = false 52 | 53 | config.secret_key_base = ENV["SECRET_KEY_BASE"] 54 | 55 | # Ignore bad email addresses and do not raise email delivery errors. 56 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 57 | # config.action_mailer.raise_delivery_errors = false 58 | 59 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 60 | # the I18n.default_locale when a translation cannot be found). 61 | config.i18n.fallbacks = true 62 | 63 | # Send deprecation notices to registered listeners. 64 | config.active_support.deprecation = :notify 65 | 66 | # Use default logging formatter so that PID and timestamp are not suppressed. 67 | config.log_formatter = ::Logger::Formatter.new 68 | 69 | # Use a different logger for distributed setups. 70 | # require 'syslog/logger' 71 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 72 | 73 | if ENV["RAILS_LOG_TO_STDOUT"].present? 74 | logger = ActiveSupport::Logger.new(STDOUT) 75 | logger.formatter = config.log_formatter 76 | config.logger = ActiveSupport::TaggedLogging.new(logger) 77 | end 78 | 79 | # Do not dump schema after migrations. 80 | config.active_record.dump_schema_after_migration = false 81 | end 82 | -------------------------------------------------------------------------------- /app/controllers/sprints_controller.rb: -------------------------------------------------------------------------------- 1 | class SprintsController < ApplicationController 2 | include ValidationsHelper 3 | include VelocityHelper 4 | include BurndownHelper 5 | include MetricHelper 6 | include SprintsDoc 7 | 8 | before_action :set_sprint, only: [:show, :update, :destroy, :get_burndown] 9 | 10 | before_action only: [:index, :create] do 11 | validate_release(0, :release_id) 12 | end 13 | 14 | before_action only: [:show, :update, :destroy, :get_velocity, :get_metrics] do 15 | validate_sprint(:id, 0) 16 | end 17 | 18 | def index 19 | @sprints = @release.sprints.reverse 20 | render json: @sprints 21 | end 22 | 23 | def show 24 | render json: @sprint 25 | end 26 | 27 | def create 28 | @sprint = Sprint.new(sprint_params) 29 | if validate_sprints_date("sprint", sprint_params) 30 | @sprint.release = @release 31 | update_amount_of_sprints 32 | if @sprint.save 33 | render json: @sprint, status: :created 34 | @sprint.release = @release 35 | update_amount_of_sprints 36 | else 37 | render json: @sprint.errors, status: :unprocessable_entity 38 | end 39 | else 40 | render json: { error: "Can not create a sprint outside the range of a release" }, status: :unprocessable_entity 41 | end 42 | end 43 | 44 | def update 45 | if validate_sprints_date("sprint", sprint_params) 46 | if @sprint.update(sprint_params) 47 | render json: @sprint 48 | else 49 | render json: @sprint.errors, status: :unprocessable_entity 50 | end 51 | else 52 | render json: { error: "Can not create a story outside the range of a sprint" }, status: :unprocessable_entity 53 | end 54 | end 55 | 56 | def destroy 57 | @sprint.destroy 58 | update_amount_of_sprints 59 | end 60 | 61 | def get_velocity 62 | release = @sprint.release 63 | if release.project.is_scoring == true 64 | velocity = get_sprints_informations(release.sprints, @sprint) 65 | 66 | render json: velocity 67 | else 68 | render json: { error: "The Velocity is only available in projects that use Story Points" }, status: :unprocessable_entity 69 | end 70 | end 71 | 72 | def get_burndown 73 | project = @sprint.release.project 74 | if project.is_scoring == true 75 | burned_stories = {} 76 | coordenates = [] 77 | date_axis = [] 78 | points_axis = [] 79 | ideal_line = [] 80 | 81 | total_points = get_total_points(@sprint) 82 | burned_stories = get_burned_points(@sprint, burned_stories) 83 | 84 | range_dates = (@sprint.initial_date .. @sprint.final_date) 85 | 86 | set_dates_and_points(burned_stories, date_axis, points_axis, range_dates, total_points) 87 | days_of_sprint = date_axis.length - 1 88 | set_ideal_line(days_of_sprint, ideal_line, total_points) 89 | 90 | coordenates = { x: date_axis, y: points_axis, ideal_line: ideal_line } 91 | 92 | burned_stories = burned_stories.sort_by { |key, value| key } 93 | 94 | render json: coordenates 95 | else 96 | render json: { error: "The Burndown Chart is only available in projects that use Story Points" }, status: :unprocessable_entity 97 | end 98 | end 99 | 100 | private 101 | def set_sprint 102 | @sprint = Sprint.find(params[:id]) 103 | end 104 | 105 | def sprint_params 106 | params.require(:sprint).permit(:name, :description, :initial_date, :final_date, :release_id) 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.4 3 | # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop 4 | # to ignore them, so only the ones explicitly set in this file are enabled. 5 | DisabledByDefault: true 6 | Exclude: 7 | - '**/templates/**/*' 8 | - '**/vendor/**/*' 9 | - '**/vendor/**/.*' 10 | - '**/node_modules/**/*' 11 | - 'actionpack/lib/action_dispatch/journey/parser.rb' 12 | 13 | # Prefer &&/|| over and/or. 14 | Style/AndOr: 15 | Enabled: true 16 | 17 | # Do not use braces for hash literals when they are the last argument of a 18 | # method call. 19 | Style/BracesAroundHashParameters: 20 | Enabled: true 21 | EnforcedStyle: context_dependent 22 | 23 | # Align `when` with `case`. 24 | Layout/CaseIndentation: 25 | Enabled: true 26 | 27 | # Align comments with method definitions. 28 | Layout/CommentIndentation: 29 | Enabled: true 30 | 31 | Layout/EmptyLineAfterMagicComment: 32 | Enabled: true 33 | 34 | # In a regular class definition, no empty lines around the body. 35 | Layout/EmptyLinesAroundClassBody: 36 | Enabled: true 37 | 38 | # In a regular method definition, no empty lines around the body. 39 | Layout/EmptyLinesAroundMethodBody: 40 | Enabled: true 41 | 42 | # In a regular module definition, no empty lines around the body. 43 | Layout/EmptyLinesAroundModuleBody: 44 | Enabled: true 45 | 46 | Layout/FirstParameterIndentation: 47 | Enabled: true 48 | 49 | # Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }. 50 | Style/HashSyntax: 51 | Enabled: true 52 | 53 | # Method definitions after `private` or `protected` isolated calls need one 54 | # extra level of indentation. 55 | Layout/IndentationConsistency: 56 | Enabled: true 57 | EnforcedStyle: rails 58 | 59 | # Two spaces, no tabs (for indentation). 60 | Layout/IndentationWidth: 61 | Enabled: true 62 | 63 | Layout/SpaceAfterColon: 64 | Enabled: true 65 | 66 | Layout/SpaceAfterComma: 67 | Enabled: true 68 | 69 | Layout/SpaceAroundEqualsInParameterDefault: 70 | Enabled: true 71 | 72 | Layout/SpaceAroundKeyword: 73 | Enabled: true 74 | 75 | Layout/SpaceAroundOperators: 76 | Enabled: true 77 | 78 | Layout/SpaceBeforeFirstArg: 79 | Enabled: true 80 | 81 | # Defining a method with parameters needs parentheses. 82 | Style/MethodDefParentheses: 83 | Enabled: true 84 | 85 | # Use `foo {}` not `foo{}`. 86 | Layout/SpaceBeforeBlockBraces: 87 | Enabled: true 88 | 89 | # Use `foo { bar }` not `foo {bar}`. 90 | Layout/SpaceInsideBlockBraces: 91 | Enabled: true 92 | 93 | # Use `{ a: 1 }` not `{a:1}`. 94 | Layout/SpaceInsideHashLiteralBraces: 95 | Enabled: true 96 | 97 | Layout/SpaceInsideParens: 98 | Enabled: true 99 | 100 | # Check quotes usage according to lint rule below. 101 | Style/StringLiterals: 102 | Enabled: true 103 | EnforcedStyle: double_quotes 104 | 105 | # Detect hard tabs, no hard tabs. 106 | Layout/Tab: 107 | Enabled: true 108 | 109 | # Blank lines should not have any spaces. 110 | Layout/TrailingBlankLines: 111 | Enabled: true 112 | 113 | # No trailing whitespace. 114 | Layout/TrailingWhitespace: 115 | Enabled: true 116 | 117 | # Use quotes for string literals when they are enough. 118 | Style/UnneededPercentQ: 119 | Enabled: true 120 | 121 | # Align `end` with the matching keyword or starting expression except for 122 | # assignments, where it should be aligned with the LHS. 123 | Lint/EndAlignment: 124 | Enabled: true 125 | EnforcedStyleAlignWith: variable 126 | 127 | # Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg. 128 | Lint/RequireParentheses: 129 | Enabled: true -------------------------------------------------------------------------------- /app/docs/projects_doc.rb: -------------------------------------------------------------------------------- 1 | module ProjectsDoc 2 | extend Apipie::DSL::Concern 3 | 4 | def_param_group :project do 5 | param :name, String, "Project's name" 6 | param :description, String, "Project's description" 7 | param :created_at, Date, "Project's time of creation", allow_nil: false 8 | param :updated_at, Date, "Project's time of edition", allow_nil: false 9 | param :user_id, :number, "User's id of project's owner" 10 | param :is_project_from_github, :boolean, "Verify if project is from a github account" 11 | param :is_scoring, :boolean, "Verify if project counts story points" 12 | end 13 | 14 | api :GET, "/users/:user_id/projects", "Show projects for an user" 15 | error code: 401, desc: "Unauthorized" 16 | error code: 404, desc: "Not Found" 17 | error code: 500, desc: "Internal Server Error" 18 | description "Show all projects of a specific user" 19 | returns code: 200, desc: "Ok" do 20 | param_group :project 21 | end 22 | example <<-EOS 23 | [ 24 | { 25 | "id":1, 26 | "name":"Owla", 27 | "description":"This project helps improving classes", 28 | "created_at":"2019-04-11T15:42:34.013Z", 29 | "updated_at":"2019-04-11T15:42:34.013Z", 30 | "user_id":1, 31 | "github_slug":null, 32 | "is_project_from_github":true, 33 | "is_scoring":true 34 | }, 35 | { 36 | "id":2, 37 | "name":"Falko", 38 | "description":"This project helps agile projects", 39 | "created_at":"2019-04-11T15:42:34.044Z", 40 | "updated_at":"2019-04-11T15:42:34.044Z", 41 | "user_id":1, 42 | "github_slug":null, 43 | "is_project_from_github":true, 44 | "is_scoring":true 45 | } 46 | ] 47 | EOS 48 | def index 49 | end 50 | 51 | api :GET, "/repos", "Show a github projects list" 52 | error code: 401, desc: "Unauthorized" 53 | error code: 404, desc: "Not Found" 54 | error code: 500, desc: "Internal Server Error" 55 | description "Show a projects list of current user from github" 56 | returns code: 200, desc: "Ok" do 57 | param_group :project 58 | end 59 | def github_projects_list 60 | end 61 | 62 | api :GET, "/projects/:id", "Show a specific project" 63 | error code: 401, desc: "Unauthorized" 64 | error code: 404, desc: "Not Found" 65 | error code: 500, desc: "Internal Server Error" 66 | returns code: 200, desc: "Ok" do 67 | param_group :project 68 | end 69 | example <<-EOS 70 | { 71 | "id":1, 72 | "name":"Owla", 73 | "description":"This project helps improving classes", 74 | "created_at":"2019-04-11T15:42:34.013Z", 75 | "updated_at":"2019-04-11T15:42:34.013Z", 76 | "user_id":1, 77 | "github_slug":null, 78 | "is_project_from_github":true, 79 | "is_scoring":true 80 | } 81 | EOS 82 | def show 83 | end 84 | 85 | api :POST, "/users/:user_id/projects", "Create a project" 86 | error code: 401, desc: "Unauthorized" 87 | error code: 404, desc: "Not Found" 88 | error code: 500, desc: "Internal Server Error" 89 | description "Create project for a specific user" 90 | param_group :project 91 | def create 92 | end 93 | 94 | api :PATCH, "/projects/:id", "Update a project" 95 | error code: 401, desc: "Unauthorized" 96 | error code: 404, desc: "Not Found" 97 | error code: 500, desc: "Internal Server Error" 98 | description "Update project for a specific user" 99 | param_group :project 100 | def update 101 | end 102 | 103 | api :DELETE, "/projects/:id", "Delete a project" 104 | error code: 401, desc: "Unauthorized" 105 | error code: 404, desc: "Not Found" 106 | error code: 500, desc: "Internal Server Error" 107 | description "Delete project for a specific user" 108 | param_group :project 109 | def destroy 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /test/helpers/metric_helper_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class VelocityHelperTest < ActiveSupport::TestCase 4 | include VelocityHelper 5 | include MetricHelper 6 | 7 | def setup 8 | @user = User.create( 9 | name: "Ronaldo", 10 | email: "Ronaldofenomeno@gmail.com", 11 | password: "123456789", 12 | password_confirmation: "123456789" 13 | ) 14 | 15 | @project = Project.create( 16 | name: "Falko", 17 | description: "Some project description.", 18 | user_id: @user.id, 19 | is_project_from_github: true, 20 | is_scoring: true 21 | ) 22 | 23 | @release = Release.create( 24 | name: "R1", 25 | description: "Description", 26 | initial_date: "01/01/2018", 27 | final_date: "01/01/2019", 28 | amount_of_sprints: "20", 29 | project_id: @project.id 30 | ) 31 | 32 | @first_sprint = Sprint.create( 33 | name: "Sprint 1", 34 | description: "Sprint 1 us10", 35 | initial_date: "06/10/2016", 36 | final_date: "13/10/2018", 37 | release_id: @release.id, 38 | ) 39 | 40 | @second_sprint = Sprint.create( 41 | name: "Sprint 2", 42 | description: "Sprint 2 us10", 43 | initial_date: "06/10/2016", 44 | final_date: "13/10/2018", 45 | release_id: @release.id, 46 | ) 47 | 48 | @first_story = Story.create( 49 | name: "Story 1", 50 | description: "Story 1 us14", 51 | assign: "Lucas", 52 | pipeline: "In Progress", 53 | initial_date: "01/01/2017", 54 | issue_number: "8", 55 | sprint_id: @first_sprint.id, 56 | story_points: "5" 57 | ) 58 | 59 | @second_story = Story.create( 60 | name: "Story 2", 61 | description: "Story 2 us14", 62 | assign: "Lucas", 63 | pipeline: "Done", 64 | initial_date: "01/01/2017", 65 | final_date: "01/02/2017", 66 | issue_number: "9", 67 | sprint_id: @first_sprint.id, 68 | story_points: "8" 69 | ) 70 | 71 | @third_story = Story.create( 72 | name: "Story 3", 73 | description: "Story 3 us14", 74 | assign: "Lucas", 75 | pipeline: "Done", 76 | initial_date: "01/01/2017", 77 | final_date: "01/02/2017", 78 | issue_number: "10", 79 | sprint_id: @first_sprint.id, 80 | story_points: "3" 81 | ) 82 | 83 | @another_first_story = Story.create( 84 | name: "Story 1", 85 | description: "Story 1 us13", 86 | assign: "Lucas", 87 | pipeline: "Done", 88 | initial_date: "01/02/2017", 89 | final_date: "01/03/2017", 90 | issue_number: "1", 91 | sprint_id: @second_sprint.id, 92 | story_points: "13" 93 | ) 94 | 95 | @another_second_story = Story.create( 96 | name: "Story 1", 97 | description: "Story 1 us13", 98 | assign: "Lucas", 99 | pipeline: "Done", 100 | initial_date: "01/02/2017", 101 | final_date: "01/03/2017", 102 | issue_number: "2", 103 | sprint_id: @second_sprint.id, 104 | story_points: "5" 105 | ) 106 | 107 | @token = AuthenticateUser.call(@user.email, @user.password) 108 | end 109 | 110 | test "Should calculate debts metric" do 111 | grade_value_test = 0 112 | grades = {} 113 | 114 | planned_points = @another_first_story.story_points + 115 | @another_second_story.story_points 116 | burned_points = @another_first_story.story_points + 117 | @another_second_story.story_points 118 | 119 | metric_value_test = Float(planned_points - burned_points) / planned_points 120 | 121 | if metric_value_test <= 0.2 122 | grade_value_test += 4 123 | elsif metric_value_test <= 0.4 124 | grade_value_test += 3 125 | elsif metric_value_test <= 0.6 126 | grade_value_test += 2 127 | elsif metric_value_test <= 0.9 128 | grade_value_test += 1 129 | elsif metric_value_test <= 1 130 | grade_value_test += 0 131 | end 132 | 133 | grades = calculate_metrics(@release) 134 | grade_value = grades[:metric_debts_value] 135 | 136 | assert_equal grade_value_test, grade_value 137 | end 138 | end 139 | -------------------------------------------------------------------------------- /test/controllers/passwords_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "bcrypt" 3 | 4 | class PasswordsControllerTest < ActionDispatch::IntegrationTest 5 | def setup 6 | @user = User.create( 7 | "name": "Eduardo", 8 | "email": "eduardo@gmail.com", 9 | "password": "asecurepassword", 10 | "password_confirmation": "asecurepassword" 11 | ) 12 | end 13 | 14 | test "should create token on forgot password request" do 15 | post "/password/forgot/", params: { 16 | "email": "eduardo@gmail.com" 17 | } 18 | assert_response :ok 19 | end 20 | 21 | test "should not create token given a non-existe email" do 22 | post "/password/forgot/", params: { 23 | "email": "nonexistent@gmail.com" 24 | } 25 | assert_response :ok 26 | end 27 | 28 | test "should not create token given a blank email" do 29 | post "/password/forgot/", params: { 30 | "email": "" 31 | } 32 | assert_response :bad_request 33 | end 34 | 35 | test "should update password given a valid token" do 36 | post "/password/forgot", params: { 37 | "email": "eduardo@gmail.com" 38 | } 39 | 40 | token = User.find_by(id: @user.id).reset_password_token 41 | 42 | post "/password/reset/", params: { 43 | "token": token, 44 | "password": "anewsecurepassword", 45 | "password_confirmation": "anewsecurepassword" 46 | } 47 | assert_response :ok 48 | 49 | updated_user = User.find_by(id: @user.id) 50 | new_password = BCrypt::Password.new(updated_user.password_digest) 51 | assert(new_password == "anewsecurepassword") 52 | assert_nil(updated_user.reset_password_token) 53 | end 54 | 55 | test "should not update password given an expired token" do 56 | post "/password/forgot/", params: { 57 | "email": "eduardo@gmail.com" 58 | } 59 | travel 5.hour 60 | token = User.find_by(id: @user.id).reset_password_token 61 | post "/password/reset/", params: { 62 | "token": token, 63 | "password": "anewsecurepassword", 64 | "password_confirmiation": "anewsecurepassword" 65 | } 66 | assert_response :not_found 67 | 68 | end 69 | 70 | test "should not update password given a different token" do 71 | post "/password/forgot/", params: { 72 | "email": "eduardo@gmail.com" 73 | } 74 | assert_response :ok 75 | 76 | post "/password/reset/", params: { 77 | "token": SecureRandom.hex(10), 78 | "password": "somepassword", 79 | "password_confirmation": "somepasswowrd" 80 | } 81 | assert_response :not_found 82 | end 83 | 84 | test "should not update password given a blank token" do 85 | post "/password/reset/", params: { 86 | "token": "", 87 | "password": "somepassword", 88 | "password_confirmation": "somepassword" 89 | } 90 | assert_response :bad_request 91 | end 92 | 93 | test "should return ok given a valid token" do 94 | post "/password/forgot", params: { 95 | "email": "eduardo@gmail.com" 96 | } 97 | 98 | token = User.find_by(id: @user.id).reset_password_token 99 | 100 | get "/password/validate_token?token=#{token}" 101 | 102 | assert_response :ok 103 | body = JSON.parse(response.body) 104 | assert_equal(body["status"], "true") 105 | end 106 | 107 | test "should not return true given an expired token" do 108 | post "/password/forgot", params: { 109 | "email": "eduardo@gmail.com" 110 | } 111 | 112 | token = User.find_by(id: @user.id).reset_password_token 113 | 114 | travel 5.hour 115 | 116 | get "/password/validate_token?token=#{token}" 117 | 118 | assert_response :ok 119 | body = JSON.parse(response.body) 120 | assert_equal(body["status"], "false") 121 | assert_equal(body["error"], ["Link not valid or expired. Try generating a new one."]) 122 | end 123 | 124 | test "should not return true given an invalid token" do 125 | post "/password/forgot", params: { 126 | "email": "eduardo@gmail.com" 127 | } 128 | assert_response :ok 129 | 130 | get "/password/validate_token?token=#{SecureRandom.hex(10)}" 131 | 132 | assert_response :ok 133 | body = JSON.parse(response.body) 134 | assert_equal(body["status"], "false") 135 | assert_equal(body["error"], ["Link not valid or expired. Try generating a new one."]) 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /app/helpers/metric_helper.rb: -------------------------------------------------------------------------------- 1 | module MetricHelper 2 | include BurndownHelper 3 | include VelocityHelper 4 | 5 | def get_metrics(grade) 6 | last_release = grade.project.releases.last 7 | if last_release.blank? 8 | return nil 9 | else 10 | metrics = calculate_metrics(last_release) 11 | 12 | if metrics.blank? 13 | final_metric = 0 14 | else 15 | sum_of_weights = grade.weight_debts + grade.weight_velocity + grade.weight_burndown 16 | 17 | final_metric = (Float (grade.weight_debts * metrics[:metric_debts_value]) + 18 | (grade.weight_velocity * metrics[:metric_velocity_value]) + 19 | (grade.weight_burndown * metrics[:metric_burndown_value])) / 20 | sum_of_weights 21 | end 22 | 23 | return final_metric.round(1) 24 | end 25 | end 26 | 27 | def calculate_metrics(release) 28 | sprint = release.sprints.last 29 | if sprint.blank? || sprint.stories.blank? 30 | return nil 31 | else 32 | if release.project.is_scoring == true 33 | burned_stories = {} 34 | date_axis = [] 35 | points_axis = [] 36 | ideal_line = [] 37 | metric_burndown_array = [] 38 | amount_of_sprints = release.sprints.count 39 | metric_velocity_value = 0 40 | planned_points = 0 41 | burned_points = 0 42 | 43 | velocity = get_sprints_informations(release.sprints, sprint) 44 | total_points = get_total_points(sprint) 45 | burned_stories = get_burned_points(sprint, burned_stories) 46 | total_sprints_points = velocity[:total_points] 47 | velocities = velocity[:velocities] 48 | 49 | range_dates = (sprint.initial_date .. sprint.final_date) 50 | 51 | set_dates_and_points(burned_stories, date_axis, points_axis, range_dates, total_points) 52 | days_of_sprint = date_axis.length - 1 53 | set_ideal_line(days_of_sprint, ideal_line, total_points) 54 | ideal_burned_points = ideal_line[0] - ideal_line[1] 55 | 56 | for i in 0..(date_axis.length - 2) 57 | real_burned_points = points_axis[i] - points_axis[i + 1] 58 | burned_percentage = Float((real_burned_points).abs * 100) / ideal_burned_points 59 | metric_burndown_array.push(burned_percentage) 60 | end 61 | 62 | for i in 0..(amount_of_sprints - 1) 63 | metric_velocity_value += (total_sprints_points[i] - velocities[i]) 64 | end 65 | 66 | for i in 0..(release.sprints.length - 1) 67 | planned_points += velocity[:total_points][i] 68 | burned_points += velocity[:completed_points][i] 69 | end 70 | 71 | metric_burndown_value = calculate_burndown(metric_burndown_array) 72 | 73 | metric_debts_value = Float(planned_points - burned_points) / planned_points 74 | metric_debts_value = calculate_velocity_and_debt(metric_debts_value) 75 | 76 | total_points = get_total_points_release(release) 77 | metric_velocity_value = Float metric_velocity_value / total_points 78 | metric_velocity_value = calculate_velocity_and_debt(metric_velocity_value) 79 | 80 | return metrics = { metric_debts_value: metric_debts_value, 81 | metric_velocity_value: metric_velocity_value, 82 | metric_burndown_value: metric_burndown_value } 83 | end 84 | end 85 | end 86 | 87 | def calculate_velocity_and_debt(metric) 88 | values = 0 89 | 90 | if metric <= 0.2 91 | values += 4 92 | elsif metric <= 0.4 93 | values += 3 94 | elsif metric <= 0.6 95 | values += 2 96 | elsif metric <= 0.9 97 | values += 1 98 | elsif metric <= 1 99 | values += 0 100 | end 101 | 102 | return values 103 | end 104 | 105 | def calculate_burndown(metric) 106 | values = 0 107 | 108 | for i in 0..(metric.length - 1) 109 | if metric[i] <= 10 || metric[i] >= 200 110 | values += 0 111 | elsif metric[i] <= 40 112 | values += 1 113 | elsif metric[i] <= 60 114 | values += 2 115 | elsif metric[i] <= 80 116 | values += 3 117 | elsif metric[i] <= 100 118 | values += 4 119 | end 120 | end 121 | 122 | return Float values / metric.length 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /test/helpers/burndown_helper_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class VelocityHelperTest < ActiveSupport::TestCase 4 | include VelocityHelper 5 | 6 | def setup 7 | @user = User.create( 8 | name: "Ronaldo", 9 | email: "Ronaldofenomeno@gmail.com", 10 | password: "123456789", 11 | password_confirmation: "123456789" 12 | ) 13 | 14 | @project = Project.create( 15 | name: "Falko", 16 | description: "Some project description.", 17 | user_id: @user.id, 18 | is_project_from_github: true, 19 | is_scoring: true 20 | ) 21 | 22 | @release = Release.create( 23 | name: "R1", 24 | description: "Description", 25 | initial_date: "01/01/2018", 26 | final_date: "01/01/2019", 27 | amount_of_sprints: "20", 28 | project_id: @project.id 29 | ) 30 | 31 | @first_sprint = Sprint.create( 32 | name: "Sprint 1", 33 | description: "Sprint 1 us10", 34 | initial_date: "06/10/2016", 35 | final_date: "13/10/2018", 36 | release_id: @release.id, 37 | ) 38 | 39 | @second_sprint = Sprint.create( 40 | name: "Sprint 2", 41 | description: "Sprint 2 us10", 42 | initial_date: "06/10/2016", 43 | final_date: "13/10/2018", 44 | release_id: @release.id, 45 | ) 46 | 47 | @first_story = Story.create( 48 | name: "Story 1", 49 | description: "Story 1 us14", 50 | assign: "Lucas", 51 | pipeline: "In Progress", 52 | initial_date: "01/01/2017", 53 | issue_number: "8", 54 | sprint_id: @first_sprint.id, 55 | story_points: "5" 56 | ) 57 | 58 | @second_story = Story.create( 59 | name: "Story 2", 60 | description: "Story 2 us14", 61 | assign: "Lucas", 62 | pipeline: "Done", 63 | initial_date: "01/01/2017", 64 | final_date: "01/02/2017", 65 | issue_number: "9", 66 | sprint_id: @first_sprint.id, 67 | story_points: "8" 68 | ) 69 | 70 | @third_story = Story.create( 71 | name: "Story 3", 72 | description: "Story 3 us14", 73 | assign: "Lucas", 74 | pipeline: "Done", 75 | initial_date: "01/01/2017", 76 | final_date: "01/02/2017", 77 | issue_number: "10", 78 | sprint_id: @first_sprint.id, 79 | story_points: "3" 80 | ) 81 | 82 | @another_first_story = Story.create( 83 | name: "Story 1", 84 | description: "Story 1 us13", 85 | assign: "Lucas", 86 | pipeline: "Done", 87 | initial_date: "01/02/2017", 88 | final_date: "01/03/2017", 89 | issue_number: "1", 90 | sprint_id: @second_sprint.id, 91 | story_points: "13" 92 | ) 93 | 94 | @another_second_story = Story.create( 95 | name: "Story 1", 96 | description: "Story 1 us13", 97 | assign: "Lucas", 98 | pipeline: "Done", 99 | initial_date: "01/02/2017", 100 | final_date: "01/03/2017", 101 | issue_number: "2", 102 | sprint_id: @second_sprint.id, 103 | story_points: "5" 104 | ) 105 | 106 | @token = AuthenticateUser.call(@user.email, @user.password) 107 | end 108 | 109 | test "Should get burned sprint points" do 110 | final_date = @another_first_story.final_date 111 | 112 | burned_stories_test = {} 113 | burned_stories = {} 114 | 115 | burned_stories_test[final_date] = 116 | @another_first_story.story_points + 117 | @another_second_story.story_points 118 | 119 | 120 | assert_equal burned_stories_test, get_burned_points(@second_sprint, burned_stories) 121 | end 122 | 123 | test "Should set ideal axis" do 124 | date_axis_test = [] 125 | ideal_line_test = [] 126 | ideal_line = [] 127 | 128 | planned_points = @another_first_story.story_points + 129 | @another_second_story.story_points 130 | 131 | range_dates = (@another_first_story.initial_date .. @another_first_story.final_date) 132 | 133 | range_dates.each do |date| 134 | date_axis_test.push(date) 135 | end 136 | 137 | days_of_sprint = date_axis_test.length - 1 138 | 139 | for day in (days_of_sprint).downto(0) 140 | ideal_line_test.push(planned_points * (day / (Float days_of_sprint))) 141 | end 142 | 143 | set_ideal_line(days_of_sprint, ideal_line, planned_points) 144 | 145 | assert_equal ideal_line_test, ideal_line 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 20190418161604) do 14 | 15 | # These are extensions that must be enabled in order to support this database 16 | enable_extension "plpgsql" 17 | 18 | create_table "grades", force: :cascade do |t| 19 | t.float "weight_burndown" 20 | t.float "weight_velocity" 21 | t.float "weight_debts" 22 | t.datetime "created_at", null: false 23 | t.datetime "updated_at", null: false 24 | t.bigint "project_id" 25 | t.index ["project_id"], name: "index_grades_on_project_id" 26 | end 27 | 28 | create_table "projects", force: :cascade do |t| 29 | t.string "name" 30 | t.text "description" 31 | t.datetime "created_at", null: false 32 | t.datetime "updated_at", null: false 33 | t.bigint "user_id" 34 | t.string "github_slug" 35 | t.boolean "is_project_from_github" 36 | t.boolean "is_scoring" 37 | t.index ["user_id"], name: "index_projects_on_user_id" 38 | end 39 | 40 | create_table "releases", force: :cascade do |t| 41 | t.string "name" 42 | t.text "description" 43 | t.integer "amount_of_sprints" 44 | t.datetime "created_at", null: false 45 | t.datetime "updated_at", null: false 46 | t.bigint "project_id" 47 | t.date "initial_date" 48 | t.date "final_date" 49 | t.index ["project_id"], name: "index_releases_on_project_id" 50 | end 51 | 52 | create_table "retrospectives", force: :cascade do |t| 53 | t.text "sprint_report" 54 | t.text "positive_points", default: [], array: true 55 | t.text "negative_points", default: [], array: true 56 | t.text "improvements", default: [], array: true 57 | t.datetime "created_at", null: false 58 | t.datetime "updated_at", null: false 59 | t.bigint "sprint_id" 60 | t.index ["sprint_id"], name: "index_retrospectives_on_sprint_id" 61 | end 62 | 63 | create_table "revisions", force: :cascade do |t| 64 | t.text "done_report", array: true 65 | t.text "undone_report", array: true 66 | t.datetime "created_at", null: false 67 | t.datetime "updated_at", null: false 68 | t.bigint "sprint_id" 69 | t.index ["sprint_id"], name: "index_revisions_on_sprint_id" 70 | end 71 | 72 | create_table "sprints", force: :cascade do |t| 73 | t.string "name" 74 | t.text "description" 75 | t.datetime "created_at", null: false 76 | t.datetime "updated_at", null: false 77 | t.bigint "release_id" 78 | t.date "initial_date" 79 | t.date "final_date" 80 | t.index ["release_id"], name: "index_sprints_on_release_id" 81 | end 82 | 83 | create_table "stories", force: :cascade do |t| 84 | t.string "name" 85 | t.text "description" 86 | t.string "assign" 87 | t.string "pipeline" 88 | t.datetime "created_at", null: false 89 | t.datetime "updated_at", null: false 90 | t.bigint "sprint_id" 91 | t.date "initial_date" 92 | t.date "final_date" 93 | t.integer "story_points" 94 | t.string "issue_number" 95 | t.integer "issue_id" 96 | t.index ["sprint_id"], name: "index_stories_on_sprint_id" 97 | end 98 | 99 | create_table "users", force: :cascade do |t| 100 | t.string "name" 101 | t.string "email" 102 | t.string "password_digest" 103 | t.datetime "created_at", null: false 104 | t.datetime "updated_at", null: false 105 | t.string "access_token" 106 | t.string "reset_password_token" 107 | t.datetime "reset_password_sent_at" 108 | end 109 | 110 | add_foreign_key "grades", "projects" 111 | add_foreign_key "projects", "users" 112 | add_foreign_key "releases", "projects" 113 | add_foreign_key "retrospectives", "sprints" 114 | add_foreign_key "revisions", "sprints" 115 | add_foreign_key "sprints", "releases" 116 | add_foreign_key "stories", "sprints" 117 | end 118 | -------------------------------------------------------------------------------- /app/controllers/issues_controller.rb: -------------------------------------------------------------------------------- 1 | require "rest-client" 2 | 3 | class IssuesController < ApplicationController 4 | include IssueGraphicHelper 5 | 6 | before_action :set_authorization, except: [:update_assignees] 7 | before_action :set_project 8 | 9 | def index 10 | client = Adapter::GitHubIssue.new(request) 11 | 12 | if params[:page].present? 13 | @issues = client.list_issues(@project.github_slug, params[:page]) 14 | total_pages = client.total_issues_pages(params[:page]) 15 | else 16 | @issues = client.list_all_issues(@project.github_slug) 17 | total_pages = 1 18 | end 19 | 20 | convert_form_params(@issues) 21 | 22 | all_stories = Story.all 23 | 24 | all_stories_number = [] 25 | 26 | all_stories.each do |story| 27 | all_stories_number.push(story.issue_number.to_i) 28 | end 29 | 30 | @filter_form = { issues_infos: [], total_pages: total_pages } 31 | 32 | @filter_form[:issues_infos] = @form_params[:issues_infos].reject do |h| 33 | all_stories_number.include? h[:issue_id] 34 | end 35 | 36 | render json: @filter_form 37 | end 38 | 39 | def create 40 | client = Adapter::GitHubIssue.new(request) 41 | 42 | @issue = client.create_issue(@project.github_slug, issue_params) 43 | 44 | convert_form_params(@issue) 45 | 46 | render json: @form_params, status: :created 47 | end 48 | 49 | def update 50 | client = Adapter::GitHubIssue.new(request) 51 | 52 | @issue = client.update_issue(@project.github_slug, issue_params) 53 | 54 | convert_form_params(@issue) 55 | 56 | render json: @form_params 57 | end 58 | 59 | def close 60 | client = Adapter::GitHubIssue.new(request) 61 | 62 | client.close_issue(@project.github_slug, issue_params) 63 | 64 | render status: :ok 65 | end 66 | 67 | def issue_graphic_data 68 | client = Adapter::GitHubIssue.new(request) 69 | 70 | @issues = client.list_all_issues(@project.github_slug) 71 | 72 | if @issues.count != 0 73 | 74 | actual_date = params[:actual_date].to_date 75 | option = params[:option] 76 | 77 | data_of_issues = {} 78 | 79 | data_of_issues = get_issues_graphic(actual_date, option, @issues) 80 | 81 | render json: data_of_issues 82 | else 83 | render json: { error: "Issues don't exists" }, status: :not_found 84 | end 85 | end 86 | 87 | def reopen_issue 88 | client = Adapter::GitHubIssue.new(request) 89 | 90 | client.reopen_issue(@project.github_slug, issue_params) 91 | 92 | render status: 200 93 | end 94 | 95 | def update_assignees 96 | begin 97 | set_project 98 | 99 | @current_user = AuthorizeApiRequest.call(request.headers).result 100 | 101 | response = RestClient.patch("https://api.github.com/repos/#{@project.github_slug}/issues/#{params[:issue_number]}", 102 | { assignees: params[:assignees] }.to_json, 103 | Authorization: "token #{@current_user.access_token}" 104 | ) 105 | 106 | render status: :ok 107 | rescue RestClient::UnprocessableEntity 108 | render json: { errors: "Cannot update assignees" }, status: :unprocessable_entity 109 | rescue RestClient::NotFound 110 | render json: { errors: "Content not found" }, status: :not_found 111 | rescue ActiveRecord::RecordNotFound 112 | render json: { errors: "Project not found" }, status: :not_found 113 | end 114 | end 115 | 116 | private 117 | 118 | def set_authorization 119 | client = Adapter::GitHubIssue.new(request) 120 | end 121 | 122 | def set_project 123 | begin 124 | @project = Project.find(params[:id]) 125 | rescue ActiveRecord::RecordNotFound 126 | render json: { errors: "Project not found" }, status: :not_found 127 | end 128 | end 129 | 130 | def convert_form_params(issue) 131 | @form_params = { issues_infos: [] } 132 | if issue.kind_of?(Array) 133 | @issues.each do |issue| 134 | make_form_params(issue) 135 | end 136 | else 137 | make_form_params(issue) 138 | end 139 | @form_params 140 | end 141 | 142 | def assignee_counter(issue) 143 | assignees = [] 144 | if issue.assignees.count > 0 145 | issue.assignees.each do |assignee| 146 | assignees.push(assignee.login) 147 | end 148 | end 149 | assignees 150 | end 151 | 152 | def make_form_params(issue) 153 | assignees = assignee_counter(issue) 154 | @form_params[:issues_infos].push(name: issue.title, number: issue.number, body: issue.body, issue_id: issue.id, assignees: assignees) unless issue.pull_request 155 | end 156 | 157 | def issue_params 158 | params.require(:issue).permit(:name, :body, :number) 159 | end 160 | end 161 | -------------------------------------------------------------------------------- /test/helpers/velocity_helper_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class VelocityHelperTest < ActiveSupport::TestCase 4 | include VelocityHelper 5 | 6 | def setup 7 | @user = User.create( 8 | name: "Ronaldo", 9 | email: "Ronaldofenomeno@gmail.com", 10 | password: "123456789", 11 | password_confirmation: "123456789" 12 | ) 13 | 14 | @project = Project.create( 15 | name: "Falko", 16 | description: "Some project description.", 17 | user_id: @user.id, 18 | is_project_from_github: true, 19 | is_scoring: true 20 | ) 21 | 22 | @release = Release.create( 23 | name: "R1", 24 | description: "Description", 25 | initial_date: "01/01/2018", 26 | final_date: "01/01/2019", 27 | amount_of_sprints: "20", 28 | project_id: @project.id 29 | ) 30 | 31 | @first_sprint = Sprint.create( 32 | name: "Sprint 1", 33 | description: "Sprint 1 us10", 34 | initial_date: "06/10/2016", 35 | final_date: "13/10/2018", 36 | release_id: @release.id, 37 | ) 38 | 39 | @second_sprint = Sprint.create( 40 | name: "Sprint 2", 41 | description: "Sprint 2 us10", 42 | initial_date: "06/10/2016", 43 | final_date: "13/10/2018", 44 | release_id: @release.id, 45 | ) 46 | 47 | @first_story = Story.create( 48 | name: "Story 1", 49 | description: "Story 1 us14", 50 | assign: "Lucas", 51 | pipeline: "In Progress", 52 | initial_date: "01/01/2017", 53 | issue_number: "8", 54 | sprint_id: @first_sprint.id, 55 | story_points: "5" 56 | ) 57 | 58 | @second_story = Story.create( 59 | name: "Story 2", 60 | description: "Story 2 us14", 61 | assign: "Lucas", 62 | pipeline: "Done", 63 | initial_date: "01/01/2017", 64 | final_date: "01/02/2017", 65 | issue_number: "9", 66 | sprint_id: @first_sprint.id, 67 | story_points: "8" 68 | ) 69 | 70 | @third_story = Story.create( 71 | name: "Story 3", 72 | description: "Story 3 us14", 73 | assign: "Lucas", 74 | pipeline: "Done", 75 | initial_date: "01/01/2017", 76 | final_date: "01/02/2017", 77 | issue_number: "10", 78 | sprint_id: @first_sprint.id, 79 | story_points: "3" 80 | ) 81 | 82 | @another_first_story = Story.create( 83 | name: "Story 1", 84 | description: "Story 1 us13", 85 | assign: "Lucas", 86 | pipeline: "Done", 87 | initial_date: "01/02/2017", 88 | final_date: "01/03/2017", 89 | issue_number: "1", 90 | sprint_id: @second_sprint.id, 91 | story_points: "13" 92 | ) 93 | 94 | @another_second_story = Story.create( 95 | name: "Story 1", 96 | description: "Story 1 us13", 97 | assign: "Lucas", 98 | pipeline: "Done", 99 | initial_date: "01/02/2017", 100 | final_date: "01/03/2017", 101 | issue_number: "2", 102 | sprint_id: @second_sprint.id, 103 | story_points: "5" 104 | ) 105 | 106 | @token = AuthenticateUser.call(@user.email, @user.password) 107 | end 108 | 109 | test "should get correct sprints informations" do 110 | sprints = [] 111 | sprints.push(@first_sprint) 112 | 113 | velocity = get_sprints_informations(sprints, @first_sprint) 114 | 115 | total_points = @first_story.story_points + @second_story.story_points + @third_story.story_points 116 | 117 | total_completed_points = @second_story.story_points + @third_story.story_points 118 | 119 | assert_equal [@first_sprint.name], velocity[:names] 120 | assert_equal [total_points], velocity[:total_points] 121 | assert_equal [total_completed_points], velocity[:completed_points] 122 | assert_equal [calculate_velocity([total_completed_points])], velocity[:velocities] 123 | end 124 | 125 | test "should calculate velocity" do 126 | sprint_1_points = @second_story.story_points + 127 | @third_story.story_points 128 | 129 | sprint_2_points = @another_first_story.story_points + 130 | @another_second_story.story_points 131 | 132 | completed_points = [] 133 | completed_points.push(sprint_1_points) 134 | completed_points.push(sprint_2_points) 135 | 136 | velocity = Float(sprint_1_points + sprint_2_points) / 2 137 | 138 | assert_equal velocity, calculate_velocity(completed_points) 139 | end 140 | 141 | test "Should calculate total points of one Release" do 142 | sprint_1_points = @first_story.story_points + 143 | @second_story.story_points + 144 | @third_story.story_points 145 | 146 | sprint_2_points = @another_first_story.story_points + 147 | @another_second_story.story_points 148 | 149 | total_points = sprint_1_points + sprint_2_points 150 | 151 | assert_equal total_points, get_total_points_release(@release) 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 |

Falko

7 | 8 |

9 | See beyond 10 |
11 | Visit our Wiki » 12 |
13 |
14 | Access Falko 15 | · 16 | FrontEnd Repository 17 | · 18 | Report an Issue 19 |

20 |

21 | 22 |

23 | Code Climate GPA 24 | Code Climate Coverage 25 | 26 | License 27 |

28 | 29 | ## Introduction 30 | 31 | Falko is an web application, developed using Ruby on Rails API and Vue.js. It is a Freen Software Project, developed initially to GPP/MDS subjects of UnB - University of Brasília - Engineering Campus (FGA). Falko aims to promote a platform that makes easier for managers _to manage_ agile projects through displaying metrics and relevant info regarding the project, also contributing to more eficient decision making. 32 | 33 | ## Development setup 34 | 35 | Development environment uses the containers architecture through _Docker_. To install, simply follow the guide [How to Use Docker](https://github.com/fga-gpp-mds/Falko-2017.2-BackEnd/wiki/How-to-Use-Docker). 36 | If you're not familiar with docker, you can just simplify some default commands with _make_. 37 | 38 | ### *_Make_ usage* 39 | #### Enters the Rails Console 40 | `$ make console` 41 | 42 | #### Creates the development and test database 43 | `$ make create-db` 44 | 45 | #### Drops down the docker environment 46 | `$ make down` 47 | 48 | #### Migrates the Rails database 49 | `$ make migrate` 50 | 51 | #### Lists all your running services 52 | `$ make ps` 53 | 54 | #### Removes all your docker networks 55 | `$ make rm-network` 56 | 57 | #### Removes all your docker mapped volumes 58 | `$ make rm-volume` 59 | 60 | #### Creates the Falko environment and run it 61 | `$ make run` 62 | 63 | #### Creates the Falko environment and run it as daemon 64 | `$ make quiet-run` 65 | 66 | #### Creates only the API environment, without database service 67 | `$ make run-api` 68 | 69 | #### Creates only the Postgres database environment, without API service 70 | `$ make run-db` 71 | 72 | #### Seeds the Rails database environment 73 | `$ make seed` 74 | 75 | #### Executes all of Rails' tests 76 | `$ make test` 77 | 78 | ### Docker Usage 79 | * Download and install Docker CE at the [official site](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-from-a-package). 80 | 81 | * Download and install Docker Compose at the [official site](https://docs.docker.com/compose/install/#master-builds). 82 | 83 | #### Clone the repository and enter it 84 | ``` 85 | git clone https://github.com/falko-org/Falko-API && cd Falko-API/ 86 | ``` 87 | 88 | #### Make sure you're at _devel_ branch 89 | ``` 90 | git checkout devel 91 | ``` 92 | 93 | #### Lift your environment 94 | ``` 95 | docker-compose up 96 | ``` 97 | 98 | #### Useful commands 99 | #### How to download a docker image 100 | ``` 101 | docker pull imageYouWant 102 | ``` 103 | 104 | #### Listing local images 105 | ``` 106 | docker images 107 | ``` 108 | 109 | #### Deleting images 110 | ``` 111 | docker rmi -f imageId 112 | ``` 113 | 114 | #### Listing running containers 115 | ``` 116 | docker ps 117 | ``` 118 | 119 | #### Removing containers 120 | ``` 121 | docker rm [-f] containerNameOrId 122 | ``` 123 | 124 | #### Executing commands from outside the container 125 | ``` 126 | docker exec 127 | ``` 128 | Example: 129 | ``` 130 | docker exec falko-api rails generate model User name:string 131 | ``` 132 | 133 | ## Documentation 134 | 135 | Additional documentation is avaiable at [Official Wiki](https://github.com/fga-gpp-mds/Falko-2017.2-BackEnd/wiki). 136 | 137 | ## How to Contribute 138 | 139 | To contribute with us, the colaborator must _fork_ and send a [pull request](https://github.com/fga-gpp-mds/Falko-2017.2-BackEnd/pulls) with his/her contribution to _devel_ branch. 140 | The code will be analized by one of the project's owners and, if approved, included to the application's core. 141 | 142 | ## License 143 | 144 | [MIT](https://github.com/fga-gpp-mds/Falko-2017.2-BackEnd/blob/devel/LICENSE) 145 | 146 | Copyright (c) 2018 Falko Organization 147 | -------------------------------------------------------------------------------- /app/docs/stories_doc.rb: -------------------------------------------------------------------------------- 1 | module StoriesDoc 2 | extend Apipie::DSL::Concern 3 | 4 | def_param_group :storie do 5 | param :name, String, "Storie's name" 6 | param :description, String, "Storie's description" 7 | param :assign, String, "Storie's assigned person" 8 | param :pipeline, String, "Storie's pipeline" 9 | param :created_at, Date, "Storie's time of creation", allow_nil: false 10 | param :updated_at, Date, "Storie's time of edition", allow_nil: false 11 | param :sprint_id, :number, "Id of sprint that the storie belongs" 12 | param :initial_date, Date, "Storie's initial date" 13 | param :final_date, Date, "Storie's final date" 14 | param :story_points, Integer, "Storie's quantity of points" 15 | param :issue_number, String, "Issue number of the storie" 16 | param :issue_id, Integer, "Issue's id" 17 | end 18 | 19 | api :GET, "/sprints/:sprint_id/stories", "Show stories for a sprint" 20 | error code: 401, desc: "Unauthorized" 21 | error code: 404, desc: "Not Found" 22 | error code: 500, desc: "Internal Server Error" 23 | description "Show all stories of a specific sprint" 24 | returns code: 200, desc: "Ok" do 25 | param_group :storie 26 | end 27 | example <<-EOS 28 | [ 29 | { 30 | "id": 2, 31 | "name": "Story 2", 32 | "description": "Story 2 us14", 33 | "assign": "Alax", 34 | "pipeline": "Done", 35 | "created_at": "2019-04-11T15:42:34.807Z", 36 | "updated_at": "2019-04-11T15:42:34.807Z", 37 | "sprint_id": 1, 38 | "initial_date": "2017-01-01", 39 | "final_date": "2017-01-08", 40 | "story_points": 3, 41 | "issue_number": "2", 42 | "issue_id": null 43 | }, 44 | { 45 | "id": 1, 46 | "name": "Story 1", 47 | "description": "Story 1 us14", 48 | "assign": "Lucas", 49 | "pipeline": "Backlog", 50 | "created_at": "2019-04-11T15:42:34.788Z", 51 | "updated_at": "2019-04-11T15:42:34.788Z", 52 | "sprint_id": 1, 53 | "initial_date": "2017-01-01", 54 | "final_date": "2017-01-02", 55 | "story_points": 2, 56 | "issue_number": "1", 57 | "issue_id": null 58 | } 59 | ] 60 | EOS 61 | def index 62 | end 63 | 64 | api :GET, "/sprints/:id/to_do_stories", "Show stories to do" 65 | error code: 401, desc: "Unauthorized" 66 | error code: 404, desc: "Not Found" 67 | error code: 500, desc: "Internal Server Error" 68 | description "Show all stories to do of a specific sprint" 69 | returns code: 200, desc: "Ok" do 70 | param_group :storie 71 | end 72 | example <<-EOS 73 | { 74 | "stories_infos": [ 75 | { 76 | "name": "Story 1", 77 | "description": "Story 1 us14", 78 | "assign": "Lucas", 79 | "pipeline": "To Do", 80 | "initial_date": "2017-01-01", 81 | "story_points": 2, 82 | "final_date": "2017-01-02", 83 | "issue_number": "1", 84 | "id": 46 85 | } 86 | ] 87 | } 88 | EOS 89 | def to_do_list 90 | end 91 | 92 | api :GET, "/sprints/:id/doing_stories", "Show doing stories" 93 | error code: 401, desc: "Unauthorized" 94 | error code: 404, desc: "Not Found" 95 | error code: 500, desc: "Internal Server Error" 96 | description "Show all doing stories of a specific sprint" 97 | returns code: 200, desc: "Ok" do 98 | param_group :storie 99 | end 100 | example <<-EOS 101 | { 102 | "stories_infos": [ 103 | { 104 | "name": "Story 1", 105 | "description": "Story 1 us14", 106 | "assign": "Matheus B", 107 | "pipeline": "Doing", 108 | "initial_date": "2017-01-01", 109 | "story_points": 5, 110 | "final_date": "2017-01-04", 111 | "issue_number": "3", 112 | "id": 47 113 | } 114 | ] 115 | } 116 | EOS 117 | def doing_list 118 | end 119 | 120 | api :GET, "/sprints/:id/done_stories", "Show done stories" 121 | error code: 401, desc: "Unauthorized" 122 | error code: 404, desc: "Not Found" 123 | error code: 500, desc: "Internal Server Error" 124 | description "Show all done stories of a specific sprint" 125 | returns code: 200, desc: "Ok" do 126 | param_group :storie 127 | end 128 | example <<-EOS 129 | { 130 | "stories_infos": [ 131 | { 132 | "name": "Story 2", 133 | "description": "Story 2 us14", 134 | "assign": "Alax", 135 | "pipeline": "Done", 136 | "initial_date": "2017-01-01", 137 | "story_points": 3, 138 | "final_date": "2017-01-08", 139 | "issue_number": "2", 140 | "id": 2 141 | } 142 | ] 143 | } 144 | EOS 145 | def done_list 146 | end 147 | 148 | api :GET, "/stories/:id", "Show a storie" 149 | error code: 401, desc: "Unauthorized" 150 | error code: 404, desc: "Not Found" 151 | error code: 500, desc: "Internal Server Error" 152 | description "Show a specific storie" 153 | returns code: 200, desc: "Ok" do 154 | param_group :storie 155 | end 156 | example <<-EOS 157 | { 158 | "id": 1, 159 | "name": "Story 1", 160 | "description": "Story 1 us14", 161 | "assign": "Lucas", 162 | "pipeline": "Backlog", 163 | "created_at": "2019-04-11T15:42:34.788Z", 164 | "updated_at": "2019-04-11T15:42:34.788Z", 165 | "sprint_id": 1, 166 | "initial_date": "2017-01-01", 167 | "final_date": "2017-01-02", 168 | "story_points": 2, 169 | "issue_number": "1", 170 | "issue_id": null 171 | } 172 | EOS 173 | def show 174 | end 175 | 176 | api :POST, "/sprints/:sprint_id/stories", "Create a storie" 177 | error code: 401, desc: "Unauthorized" 178 | error code: 404, desc: "Not Found" 179 | error code: 500, desc: "Internal Server Error" 180 | description "Create a specific storie" 181 | param_group :storie 182 | def create 183 | end 184 | 185 | api :PATCH, "/stories/:id", "Update a storie" 186 | error code: 401, desc: "Unauthorized" 187 | error code: 404, desc: "Not Found" 188 | error code: 500, desc: "Internal Server Error" 189 | description "Update a specific storie" 190 | param_group :storie 191 | def update 192 | end 193 | 194 | api :DELETE, "/stories/:id", "Delete a storie" 195 | error code: 401, desc: "Unauthorized" 196 | error code: 404, desc: "Not Found" 197 | error code: 500, desc: "Internal Server Error" 198 | description "Delete a specific storie" 199 | param_group :storie 200 | def destroy 201 | end 202 | end 203 | -------------------------------------------------------------------------------- /app/helpers/validations_helper.rb: -------------------------------------------------------------------------------- 1 | module ValidationsHelper 2 | def current_user 3 | @current_user = AuthorizeApiRequest.call(request.headers).result 4 | end 5 | 6 | def user 7 | @user = User.find(@project.user_id) 8 | end 9 | 10 | def project 11 | @project = Project.find(@release.project_id) 12 | end 13 | 14 | def project_grade 15 | @project = Project.find(@grade.project.id) 16 | end 17 | 18 | def release 19 | @release = Release.find(@sprint.release_id) 20 | end 21 | 22 | def sprint(component_type) 23 | if component_type == "story" 24 | @sprint = Sprint.find(@story.sprint_id) 25 | elsif component_type == "revision" 26 | @sprint = Sprint.find(@revision.sprint_id) 27 | elsif component_type == "retrospective" 28 | @sprint = Sprint.find(@retrospective.sprint_id) 29 | end 30 | end 31 | 32 | def verifies_id(current_id, previous_id, component_type) 33 | if component_type == "user" && current_id != 0 34 | id = current_id 35 | @user = User.find(params[:id].to_i) 36 | elsif component_type == "user" && previous_id != 0 37 | user_id = previous_id 38 | @user = User.find(params[:user_id].to_i) 39 | elsif component_type == "project" && current_id != 0 40 | id = current_id 41 | @project = Project.find(params[:id].to_i) 42 | elsif component_type == "project" && previous_id != 0 43 | project_id = previous_id 44 | @project = Project.find(params[:project_id].to_i) 45 | elsif component_type == "release" && current_id != 0 46 | id = current_id 47 | @release = Release.find(params[:id].to_i) 48 | elsif component_type == "release" && previous_id != 0 49 | release_id = previous_id 50 | @release = Release.find(params[:release_id].to_i) 51 | elsif component_type == "grade" && current_id != 0 52 | id = current_id 53 | @grade = Grade.find(params[:id].to_i) 54 | elsif component_type == "grade" && previous_id != 0 55 | grade_id = previous_id 56 | @grade = Grade.find(params[:grade_id].to_i) 57 | elsif component_type == "sprint" && current_id != 0 58 | id = current_id 59 | @sprint = Sprint.find(params[:id].to_i) 60 | elsif component_type == "sprint" && previous_id != 0 61 | sprint_id = previous_id 62 | @sprint = Sprint.find(params[:sprint_id].to_i) 63 | end 64 | end 65 | 66 | def validate_user(id, user_id) 67 | current_user 68 | verifies_id(id, user_id, "user") 69 | 70 | if @current_user.id == @user.id 71 | return true 72 | else 73 | render json: { error: "Not Authorized" }, status: 401 74 | end 75 | end 76 | 77 | def validate_project(id, project_id) 78 | current_user 79 | verifies_id(id, project_id, "project") 80 | user 81 | 82 | if @current_user.id == @user.id 83 | return true 84 | else 85 | render json: { error: "Not Authorized" }, status: 401 86 | end 87 | end 88 | 89 | 90 | def validate_grade(id, grade_id) 91 | current_user 92 | verifies_id(id, grade_id, "grade") 93 | project_grade 94 | user 95 | 96 | if @current_user.id == @user.id 97 | return true 98 | else 99 | render json: { error: "Not Authorized" }, status: 401 100 | end 101 | end 102 | 103 | def validate_release(id, release_id) 104 | current_user 105 | verifies_id(id, release_id, "release") 106 | project 107 | user 108 | 109 | if @current_user.id == @user.id 110 | return true 111 | else 112 | render json: { error: "Not Authorized" }, status: 401 113 | end 114 | end 115 | 116 | def validate_sprint(id, sprint_id) 117 | current_user 118 | verifies_id(id, sprint_id, "sprint") 119 | release 120 | project 121 | user 122 | 123 | if @current_user.id == @user.id 124 | return true 125 | else 126 | render json: { error: "Not Authorized" }, status: 401 127 | end 128 | end 129 | 130 | def validate_sprints_date(component_type, component_params) 131 | if @release.initial_date > @sprint.initial_date || 132 | @release.final_date < @sprint.initial_date 133 | false 134 | elsif @release.final_date < @sprint.final_date || 135 | @release.initial_date > @sprint.final_date 136 | false 137 | else 138 | return true 139 | end 140 | end 141 | 142 | 143 | def validate_stories(story_points, id, sprint_id) 144 | current_user 145 | verifies_id(id, sprint_id, "sprint") 146 | release 147 | project 148 | user 149 | 150 | if @project.is_scoring 151 | if story_points != nil 152 | return true 153 | else 154 | return false 155 | end 156 | else 157 | if story_points != nil 158 | return false 159 | else 160 | return true 161 | end 162 | end 163 | end 164 | 165 | def validate_sprint_dependencies(id, component_type) 166 | current_user 167 | if component_type == "story" 168 | @story = Story.find(params[:id].to_i) 169 | sprint("story") 170 | elsif component_type == "revision" 171 | @revision = Revision.find(params[:id].to_i) 172 | sprint("revision") 173 | elsif component_type == "retrospective" 174 | @retrospective = Retrospective.find(params[:id].to_i) 175 | sprint("retrospective") 176 | end 177 | release 178 | project 179 | user 180 | 181 | if @current_user.id == @user.id 182 | return true 183 | else 184 | render json: { error: "Not Authorized" }, status: 401 185 | end 186 | end 187 | 188 | def create_sprint_dependencies(component_type, component_params) 189 | if component_type == "revision" && @sprint.revision == nil 190 | @component = Revision.create(component_params) 191 | save_component(@component) 192 | elsif component_type == "retrospective" && @sprint.retrospective == nil 193 | @component = Retrospective.create(component_params) 194 | save_component(@component) 195 | else 196 | render json: { error: "Cannot create multiple #{component_type}" }, status: 403 197 | end 198 | end 199 | 200 | def save_component(component) 201 | @component = component 202 | @component.sprint_id = @sprint.id 203 | if @component.save 204 | render json: @component, status: :created 205 | else 206 | render json: @component.errors, status: :unprocessable_entity 207 | end 208 | end 209 | 210 | def update_amount_of_sprints 211 | @release.amount_of_sprints = @release.sprints.count 212 | @release.save 213 | end 214 | end 215 | -------------------------------------------------------------------------------- /test/controllers/users_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class UsersControllerTest < ActionDispatch::IntegrationTest 4 | def setup 5 | @user = User.create( 6 | name: "Ronaldo", 7 | email: "ronaldofenomeno@gmail.com", 8 | password: "123456789", 9 | password_confirmation: "123456789" 10 | ) 11 | 12 | @token = AuthenticateUser.call(@user.email, @user.password) 13 | end 14 | 15 | test "should login in" do 16 | post "/authenticate", params: { 17 | email: "ronaldofenomeno@gmail.com", 18 | password: "123456789" 19 | } 20 | 21 | assert_response :success 22 | end 23 | 24 | test "should create valid user" do 25 | assert_difference("User.count") do 26 | post "/users", params: { 27 | "user": { 28 | "email": "robakasdddi@email.com", 29 | "name": "Fulvvano", 30 | "password": "123456789", 31 | "password_confirmation": "123456789" 32 | } 33 | }, headers: { Authorization: @token.result } 34 | 35 | assert_response :success 36 | end 37 | end 38 | 39 | test "should login with rights params" do 40 | post "/authenticate", params: { 41 | email: "ronaldofenomeno@gmail.com", 42 | password: "123456789" 43 | } 44 | 45 | assert_response :success 46 | end 47 | 48 | test "should not login with wrongs params" do 49 | post "/authenticate", params: { 50 | email: "fenomeno@gmail.com", 51 | password: "123456789" 52 | } 53 | 54 | assert_response :unauthorized 55 | end 56 | 57 | test "should create a user with valids params" do 58 | assert_difference("User.count") do 59 | post "/users", params: { 60 | "user": { 61 | "email": "robakasdddi@email.com", 62 | "name": "Fulvvano", 63 | "password": "123456789", 64 | "password_confirmation": "123456789" 65 | } 66 | } 67 | 68 | assert_response :success 69 | end 70 | end 71 | 72 | test "should not create invalid user with short name" do 73 | assert_no_difference("User.count") do 74 | post "/users", params: { 75 | "user": { 76 | "email": "robakasdddi@email.com", 77 | "name": "Fu", 78 | "password": "123456789", 79 | "password_confirmation": "123456789" 80 | } 81 | } 82 | 83 | assert_response :unprocessable_entity 84 | end 85 | end 86 | 87 | test "should not create invalid user with too large name" do 88 | assert_no_difference("User.count") do 89 | post "/users", params: { 90 | "user": { 91 | "email": "robakasdddi@email.com", 92 | "name": "F" * 81, 93 | "password": "123456789", 94 | "password_confirmation": "123456789" 95 | } 96 | } 97 | 98 | assert_response :unprocessable_entity 99 | end 100 | end 101 | 102 | 103 | test "should delete user" do 104 | delete "/users/#{@user.id}", headers: { Authorization: @token.result } 105 | 106 | assert_response :success 107 | end 108 | 109 | test "should show user" do 110 | get "/users/#{@user.id}", headers: { Authorization: @token.result } 111 | 112 | assert_response :success 113 | end 114 | 115 | test "should update user" do 116 | patch "/users/#{@user.id}", params: { 117 | "user": { 118 | "name": "Fulvvano123", 119 | } 120 | }, headers: { Authorization: @token.result } 121 | 122 | @user.reload 123 | 124 | assert_response :success 125 | end 126 | 127 | test "should not update user without authentication" do 128 | patch "/users/#{@user.id}", params: { 129 | "user": { 130 | "email": "robakasdddi@email.com", 131 | "name": "Fulvvano", 132 | } 133 | } 134 | @user.reload 135 | 136 | assert_response :unauthorized 137 | end 138 | 139 | test "should not update user with invalid params" do 140 | patch "/users/#{@user.id}", params: { 141 | "user": { 142 | "email": "robakasdddi@email.com", 143 | "name": "Fulvvano", 144 | "password": "12345", 145 | "password_confirmation": "123456789" 146 | } 147 | }, headers: { Authorization: @token.result } 148 | @user.reload 149 | 150 | assert_response :unprocessable_entity 151 | assert response.parsed_body["access_token"] != "bad_verification_code" 152 | end 153 | 154 | test "should authenticate user with github" do 155 | RestClient.stub :post, "access_token=token123&something_else=anothervalue" do 156 | post "/request_github_token", params: { 157 | "code": "code123", 158 | "id": "#{@user.id}" 159 | }, headers: { Authorization: @token.result } 160 | 161 | assert_response :success 162 | assert response.parsed_body["access_token"] != "bad_verification_code" 163 | end 164 | end 165 | 166 | test "should not authenticate with bad verification code" do 167 | RestClient.stub :post, "access_token=bad_verification_code&something_else=anothervalue" do 168 | post "/request_github_token", params: { 169 | "code": "code123", 170 | "id": "#{@user.id}" 171 | }, headers: { Authorization: @token.result } 172 | 173 | assert_response :bad_request 174 | end 175 | end 176 | 177 | test "should not authenticate with another id" do 178 | RestClient.stub :post, "access_token=token123&something_else=anothervalue" do 179 | 180 | exception = assert_raises ActiveRecord::RecordNotFound do 181 | post "/request_github_token", params: { 182 | "code": "code123", 183 | "id": "2" 184 | }, headers: { Authorization: @token.result } 185 | 186 | assert_response 500 187 | end 188 | assert_equal("Couldn't find User with 'id'=2", exception.message) 189 | end 190 | end 191 | 192 | test "should render unprocessable entity" do 193 | RestClient.stub :post, "access_token=&something_else=anothervalue" do 194 | post "/request_github_token", params: { 195 | "code": "code123", 196 | "id": "#{@user.id}" 197 | }, headers: { Authorization: @token.result } 198 | 199 | assert_response :bad_request 200 | end 201 | end 202 | 203 | test "should remove github token" do 204 | post "/remove_github_token", params: { 205 | "id": "#{@user.id}" 206 | }, headers: { Authorization: @token.result } 207 | 208 | assert_response :success 209 | end 210 | 211 | test "should not remove github token without authentication" do 212 | post "/remove_github_token", params: { 213 | "id": "#{@user.id}" 214 | } 215 | 216 | assert_response :unauthorized 217 | end 218 | end 219 | -------------------------------------------------------------------------------- /test/controllers/releases_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ReleasesControllerTest < ActionDispatch::IntegrationTest 4 | def setup 5 | @user = User.create( 6 | name: "Robert", 7 | email: "robert@email.com", 8 | password: "123123", 9 | password_confirmation: "123123" 10 | ) 11 | 12 | @project = Project.create( 13 | name: "Falko", 14 | description: "Description.", 15 | user_id: @user.id, 16 | is_project_from_github: true, 17 | is_scoring: false 18 | ) 19 | 20 | @release = Release.create( 21 | name: "R1", 22 | description: "Description", 23 | initial_date: "2018-01-01", 24 | final_date: "2019-01-01", 25 | amount_of_sprints: "20", 26 | project_id: @project.id 27 | ) 28 | 29 | @token = AuthenticateUser.call(@user.email, @user.password) 30 | 31 | @another_user = User.create(name: "Ronaldo", 32 | email: "ronaldo@email.com", 33 | password: "123123", 34 | password_confirmation: "123123" 35 | ) 36 | 37 | @another_project = Project.create( 38 | name: "Futebol", 39 | description: "Description.", 40 | user_id: @another_user.id, 41 | is_project_from_github: true 42 | ) 43 | 44 | @another_release = Release.create( 45 | name: "Real Madrid", 46 | description: "Descriptions", 47 | initial_date: "2018-01-01", 48 | final_date: "2019-01-01", 49 | amount_of_sprints: "20", 50 | project_id: @another_project.id 51 | ) 52 | 53 | @another_token = AuthenticateUser.call(@another_user.email, @another_user.password) 54 | end 55 | 56 | test "should create release" do 57 | post "/projects/#{@project.id}/releases", params: { 58 | "release": { 59 | "name": "Release 01", 60 | "description": "First Release", 61 | "amount_of_sprints": "20", 62 | "initial_date": "2018-01-01", 63 | "final_date": "2019-01-01" 64 | } 65 | }, headers: { Authorization: @token.result } 66 | 67 | assert_response :created 68 | end 69 | 70 | test "should not create release without correct params" do 71 | # Final date before initial date 72 | post "/projects/#{@project.id}/releases", params: { 73 | "release": { 74 | "name": "Release 01", 75 | "description": "First Release", 76 | "amount_of_sprints": "20", 77 | "initial_date": "2018-01-01", 78 | "final_date": "1900-01-01" 79 | } 80 | }, headers: { Authorization: @token.result } 81 | 82 | assert_response :unprocessable_entity 83 | end 84 | 85 | 86 | test "should not create release without authentication" do 87 | post "/projects/#{@project.id}/releases", params: { 88 | "release": { 89 | "name": "Release 01", 90 | "description": "First Release", 91 | "amount_of_sprints": "20", 92 | "initial_date": "2018-01-01", 93 | "final_date": "2019-01-01" 94 | } 95 | } 96 | 97 | assert_response :unauthorized 98 | end 99 | 100 | test "should not get releases index without authentication" do 101 | get "/projects/#{@project.id}/releases" 102 | assert_response :unauthorized 103 | end 104 | 105 | test "should get releases index" do 106 | get "/projects/#{@project.id}/releases", headers: { Authorization: @token.result } 107 | assert_response :success 108 | end 109 | 110 | test "should not get releases show without authentication" do 111 | get "/releases/#{@release.id}" 112 | assert_response :unauthorized 113 | end 114 | 115 | test "should get releases show" do 116 | get "/releases/#{@release.id}", headers: { Authorization: @token.result } 117 | assert_response :success 118 | end 119 | 120 | test "should edit releases" do 121 | @old_name_release = @release.name 122 | @old_description_release = @release.description 123 | @old_initial_date_release = @release.initial_date 124 | 125 | patch "/releases/#{@release.id}", params: { 126 | release: { 127 | name: "Daniboy", 128 | description: "CBlacku", 129 | initial_date: "2010-05-06" 130 | } 131 | }, headers: { Authorization: @token.result } 132 | 133 | @release.reload 134 | 135 | assert_response :ok 136 | assert_not_equal @old_name_release, @release.name 137 | assert_not_equal @old_description_release, @release.description 138 | assert_not_equal @old_initial_date_release, @release.initial_date 139 | end 140 | 141 | test "should not edit releases without authenticantion" do 142 | @old_name_release = @release.name 143 | @old_description_release = @release.description 144 | @old_initial_date_release = @release.initial_date 145 | 146 | patch "/releases/#{@release.id}", params: { 147 | release: { 148 | name: "Daniboy", 149 | description: "CBlacku", 150 | initial_date: "2010-05-06" 151 | } 152 | } 153 | 154 | @release.reload 155 | 156 | assert_response :unauthorized 157 | assert_equal @old_name_release, @release.name 158 | assert_equal @old_description_release, @release.description 159 | assert_equal @old_initial_date_release, @release.initial_date 160 | end 161 | 162 | test "should not edit releases with blank params" do 163 | @old_name_release = @release.name 164 | @old_description_release = @release.description 165 | @old_initial_date_release = @release.initial_date 166 | 167 | patch "/releases/#{@release.id}", params: { 168 | release: { 169 | name: "", 170 | description: "", 171 | initial_date: "" 172 | } 173 | }, headers: { Authorization: @token.result } 174 | 175 | @release.reload 176 | 177 | assert_response :unprocessable_entity 178 | assert_equal @old_name_release, @release.name 179 | assert_equal @old_description_release, @release.description 180 | assert_equal @old_initial_date_release, @release.initial_date 181 | end 182 | 183 | test "should destroy release" do 184 | assert_difference("Release.count", -1) do 185 | delete "/releases/#{@release.id}", headers: { Authorization: @token.result } 186 | end 187 | 188 | assert_response :no_content 189 | end 190 | 191 | test "should not destroy release without authentication" do 192 | assert_no_difference "Release.count" do 193 | delete "/releases/#{@release.id}" 194 | end 195 | 196 | assert_response :unauthorized 197 | end 198 | 199 | test "should not destroy release of another user" do 200 | delete "/releases/#{@release.id}", headers: { Authorization: @another_token.result } 201 | 202 | assert_response :unauthorized 203 | end 204 | end 205 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (5.1.6) 5 | actionpack (= 5.1.6) 6 | nio4r (~> 2.0) 7 | websocket-driver (~> 0.6.1) 8 | actionmailer (5.1.6) 9 | actionpack (= 5.1.6) 10 | actionview (= 5.1.6) 11 | activejob (= 5.1.6) 12 | mail (~> 2.5, >= 2.5.4) 13 | rails-dom-testing (~> 2.0) 14 | actionpack (5.1.6) 15 | actionview (= 5.1.6) 16 | activesupport (= 5.1.6) 17 | rack (~> 2.0) 18 | rack-test (>= 0.6.3) 19 | rails-dom-testing (~> 2.0) 20 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 21 | actionview (5.1.6) 22 | activesupport (= 5.1.6) 23 | builder (~> 3.1) 24 | erubi (~> 1.4) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 27 | activejob (5.1.6) 28 | activesupport (= 5.1.6) 29 | globalid (>= 0.3.6) 30 | activemodel (5.1.6) 31 | activesupport (= 5.1.6) 32 | activerecord (5.1.6) 33 | activemodel (= 5.1.6) 34 | activesupport (= 5.1.6) 35 | arel (~> 8.0) 36 | activesupport (5.1.6) 37 | concurrent-ruby (~> 1.0, >= 1.0.2) 38 | i18n (>= 0.7, < 2) 39 | minitest (~> 5.1) 40 | tzinfo (~> 1.1) 41 | addressable (2.4.0) 42 | ansi (1.5.0) 43 | apipie-rails (0.5.15) 44 | rails (>= 4.1) 45 | arel (8.0.0) 46 | ast (2.4.0) 47 | backports (3.11.3) 48 | bcrypt (3.1.11) 49 | builder (3.2.3) 50 | byebug (10.0.2) 51 | carrierwave (1.2.2) 52 | activemodel (>= 4.0.0) 53 | activesupport (>= 4.0.0) 54 | mime-types (>= 1.16) 55 | carrierwave-base64 (2.7.0) 56 | carrierwave (>= 0.8.0) 57 | mime-types (~> 3.0) 58 | codeclimate-test-reporter (1.0.8) 59 | simplecov (<= 0.13) 60 | concurrent-ruby (1.0.5) 61 | crass (1.0.4) 62 | devise (4.4.3) 63 | bcrypt (~> 3.0) 64 | orm_adapter (~> 0.1) 65 | railties (>= 4.1.0, < 6.0) 66 | responders 67 | warden (~> 1.2.3) 68 | docile (1.1.5) 69 | domain_name (0.5.20180417) 70 | unf (>= 0.0.5, < 1.0.0) 71 | erubi (1.7.1) 72 | ethon (0.11.0) 73 | ffi (>= 1.3.0) 74 | faraday (0.15.0) 75 | multipart-post (>= 1.2, < 3) 76 | faraday_middleware (0.12.2) 77 | faraday (>= 0.7.4, < 1.0) 78 | ffi (1.9.23) 79 | gh (0.15.1) 80 | addressable (~> 2.4.0) 81 | backports 82 | faraday (~> 0.8) 83 | multi_json (~> 1.0) 84 | net-http-persistent (~> 2.9) 85 | net-http-pipeline 86 | globalid (0.4.1) 87 | activesupport (>= 4.2.0) 88 | highline (1.7.10) 89 | http-cookie (1.0.3) 90 | domain_name (~> 0.5) 91 | i18n (1.0.1) 92 | concurrent-ruby (~> 1.0) 93 | json (2.1.0) 94 | jwt (1.5.6) 95 | launchy (2.4.3) 96 | addressable (~> 2.3) 97 | listen (3.1.5) 98 | rb-fsevent (~> 0.9, >= 0.9.4) 99 | rb-inotify (~> 0.9, >= 0.9.7) 100 | ruby_dep (~> 1.2) 101 | loofah (2.2.2) 102 | crass (~> 1.0.2) 103 | nokogiri (>= 1.5.9) 104 | mail (2.7.0) 105 | mini_mime (>= 0.1.1) 106 | method_source (0.9.0) 107 | mime-types (3.1) 108 | mime-types-data (~> 3.2015) 109 | mime-types-data (3.2016.0521) 110 | mini_mime (1.0.0) 111 | mini_portile2 (2.3.0) 112 | minitest (5.11.3) 113 | minitest-reporters (1.2.0) 114 | ansi 115 | builder 116 | minitest (>= 5.0) 117 | ruby-progressbar 118 | multi_json (1.13.1) 119 | multipart-post (2.0.0) 120 | net-http-persistent (2.9.4) 121 | net-http-pipeline (1.0.1) 122 | netrc (0.11.0) 123 | nio4r (2.3.1) 124 | nokogiri (1.8.2) 125 | mini_portile2 (~> 2.3.0) 126 | octokit (4.8.0) 127 | sawyer (~> 0.8.0, >= 0.5.3) 128 | orm_adapter (0.5.0) 129 | parallel (1.12.1) 130 | parser (2.5.1.0) 131 | ast (~> 2.4.0) 132 | pg (1.0.0) 133 | powerpack (0.1.1) 134 | puma (3.11.4) 135 | pusher-client (0.6.2) 136 | json 137 | websocket (~> 1.0) 138 | rack (2.0.5) 139 | rack-cors (1.0.2) 140 | rack-test (1.0.0) 141 | rack (>= 1.0, < 3) 142 | rails (5.1.6) 143 | actioncable (= 5.1.6) 144 | actionmailer (= 5.1.6) 145 | actionpack (= 5.1.6) 146 | actionview (= 5.1.6) 147 | activejob (= 5.1.6) 148 | activemodel (= 5.1.6) 149 | activerecord (= 5.1.6) 150 | activesupport (= 5.1.6) 151 | bundler (>= 1.3.0) 152 | railties (= 5.1.6) 153 | sprockets-rails (>= 2.0.0) 154 | rails-dom-testing (2.0.3) 155 | activesupport (>= 4.2.0) 156 | nokogiri (>= 1.6) 157 | rails-html-sanitizer (1.0.4) 158 | loofah (~> 2.2, >= 2.2.2) 159 | rails_12factor (0.0.3) 160 | rails_serve_static_assets 161 | rails_stdout_logging 162 | rails_serve_static_assets (0.0.5) 163 | rails_stdout_logging (0.0.5) 164 | railties (5.1.6) 165 | actionpack (= 5.1.6) 166 | activesupport (= 5.1.6) 167 | method_source 168 | rake (>= 0.8.7) 169 | thor (>= 0.18.1, < 2.0) 170 | rainbow (3.0.0) 171 | rake (12.3.1) 172 | rb-fsevent (0.10.3) 173 | rb-inotify (0.9.10) 174 | ffi (>= 0.5.0, < 2) 175 | responders (2.4.0) 176 | actionpack (>= 4.2.0, < 5.3) 177 | railties (>= 4.2.0, < 5.3) 178 | rest-client (2.0.2) 179 | http-cookie (>= 1.0.2, < 2.0) 180 | mime-types (>= 1.16, < 4.0) 181 | netrc (~> 0.8) 182 | rubocop (0.56.0) 183 | parallel (~> 1.10) 184 | parser (>= 2.5) 185 | powerpack (~> 0.1) 186 | rainbow (>= 2.2.2, < 4.0) 187 | ruby-progressbar (~> 1.7) 188 | unicode-display_width (~> 1.0, >= 1.0.1) 189 | ruby-progressbar (1.9.0) 190 | ruby_dep (1.5.0) 191 | sawyer (0.8.1) 192 | addressable (>= 2.3.5, < 2.6) 193 | faraday (~> 0.8, < 1.0) 194 | simple_command (0.0.9) 195 | simple_token_authentication (1.15.1) 196 | actionmailer (>= 3.2.6, < 6) 197 | actionpack (>= 3.2.6, < 6) 198 | devise (>= 3.2, < 6) 199 | simplecov (0.13.0) 200 | docile (~> 1.1.0) 201 | json (>= 1.8, < 3) 202 | simplecov-html (~> 0.10.0) 203 | simplecov-html (0.10.2) 204 | spring (2.0.2) 205 | activesupport (>= 4.2) 206 | spring-watcher-listen (2.0.1) 207 | listen (>= 2.7, < 4.0) 208 | spring (>= 1.2, < 3.0) 209 | sprockets (3.7.1) 210 | concurrent-ruby (~> 1.0) 211 | rack (> 1, < 3) 212 | sprockets-rails (3.2.1) 213 | actionpack (>= 4.0) 214 | activesupport (>= 4.0) 215 | sprockets (>= 3.0.0) 216 | thor (0.20.0) 217 | thread_safe (0.3.6) 218 | travis (1.8.8) 219 | backports 220 | faraday (~> 0.9) 221 | faraday_middleware (~> 0.9, >= 0.9.1) 222 | gh (~> 0.13) 223 | highline (~> 1.6) 224 | launchy (~> 2.1) 225 | pusher-client (~> 0.4) 226 | typhoeus (~> 0.6, >= 0.6.8) 227 | typhoeus (0.8.0) 228 | ethon (>= 0.8.0) 229 | tzinfo (1.2.5) 230 | thread_safe (~> 0.1) 231 | unf (0.1.4) 232 | unf_ext 233 | unf_ext (0.0.7.5) 234 | unicode-display_width (1.3.2) 235 | warden (1.2.7) 236 | rack (>= 1.0) 237 | websocket (1.2.5) 238 | websocket-driver (0.6.5) 239 | websocket-extensions (>= 0.1.0) 240 | websocket-extensions (0.1.3) 241 | 242 | PLATFORMS 243 | ruby 244 | 245 | DEPENDENCIES 246 | activesupport (~> 5.1, >= 5.1.4) 247 | apipie-rails (~> 0.5.11) 248 | bcrypt (~> 3.1.7) 249 | byebug 250 | carrierwave (~> 1.1) 251 | carrierwave-base64 (~> 2.5, >= 2.5.3) 252 | codeclimate-test-reporter 253 | jwt (~> 1.5.6) 254 | listen (>= 3.0.5, < 3.2) 255 | minitest (~> 5.8, >= 5.8.4) 256 | minitest-reporters 257 | octokit (~> 4.0) 258 | pg (~> 1.0.0) 259 | puma (~> 3.7) 260 | rack-cors 261 | rails (~> 5.1.6) 262 | rails_12factor 263 | rest-client 264 | rubocop 265 | simple_command 266 | simple_token_authentication (~> 1.0) 267 | simplecov 268 | spring 269 | spring-watcher-listen (~> 2.0.0) 270 | travis (~> 1.8, >= 1.8.8) 271 | tzinfo-data 272 | 273 | RUBY VERSION 274 | ruby 2.4.1p111 275 | 276 | BUNDLED WITH 277 | 1.16.2 278 | --------------------------------------------------------------------------------