├── log └── .keep ├── storage └── .keep ├── tmp ├── .keep └── pids │ └── .keep ├── vendor └── .keep ├── lib └── tasks │ └── .keep ├── test ├── mailers │ └── .keep ├── models │ └── .keep ├── controllers │ └── .keep ├── fixtures │ ├── .keep │ └── files │ │ └── .keep ├── integration │ └── .keep ├── channels │ └── application_cable │ │ └── connection_test.rb └── test_helper.rb ├── .ruby-version ├── app ├── models │ ├── concerns │ │ └── .keep │ ├── application_record.rb │ ├── category.rb │ ├── token.rb │ ├── user.rb │ └── task.rb ├── controllers │ ├── concerns │ │ └── .keep │ ├── application_controller.rb │ └── api │ │ ├── auth │ │ └── authentication_controller.rb │ │ └── tasks_controller.rb ├── views │ └── layouts │ │ ├── mailer.text.erb │ │ └── mailer.html.erb ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── mailers │ └── application_mailer.rb ├── jobs │ └── application_job.rb ├── lib │ ├── json_web_token.rb │ ├── message.rb │ └── exception_handler.rb └── auth │ ├── authenticate_user.rb │ └── authorize_api_request.rb ├── .rspec ├── mobile.PNG ├── yarn.lock ├── public └── robots.txt ├── spec ├── factories │ ├── categories.rb │ ├── tasks.rb │ └── users.rb ├── support │ ├── request_spec_helper.rb │ └── controller_spec_helper.rb ├── models │ ├── category_spec.rb │ ├── task_spec.rb │ ├── token_spec.rb │ └── user_spec.rb ├── auth │ ├── authenticate_user_spec.rb │ └── authorize_api_request_spec.rb ├── controllers │ └── application_controller_spec.rb ├── requests │ └── api │ │ ├── auth │ │ └── authentication_request_spec.rb │ │ └── tasks_request_spec.rb ├── rails_helper.rb └── spec_helper.rb ├── bin ├── rake ├── rails ├── setup └── bundle ├── config.ru ├── config ├── environment.rb ├── initializers │ ├── mime_types.rb │ ├── filter_parameter_logging.rb │ ├── application_controller_renderer.rb │ ├── backtrace_silencers.rb │ ├── cors.rb │ ├── wrap_parameters.rb │ └── inflections.rb ├── cable.yml ├── boot.rb ├── credentials.yml.enc ├── routes.rb ├── database.yml ├── locales │ └── en.yml ├── storage.yml ├── application.rb ├── puma.rb └── environments │ ├── development.rb │ ├── test.rb │ └── production.rb ├── Rakefile ├── db ├── migrate │ ├── 20200807232004_create_categories.rb │ ├── 20200807230510_create_users.rb │ ├── 20200815215500_create_tokens.rb │ └── 20200807232006_create_tasks.rb ├── seeds.rb └── schema.rb ├── .stickler.yml ├── .gitignore ├── Gemfile ├── .rubocop.yml ├── README.md └── Gemfile.lock /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/pids/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.7.1 2 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /mobile.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dandush03/FinalCapstone.BackEnd/HEAD/mobile.PNG -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | # App Records 2 | class ApplicationRecord < ActiveRecord::Base 3 | self.abstract_class = true 4 | end 5 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /spec/factories/categories.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :category do 3 | sequence(:category) { |n| "category#{n}" } 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/category.rb: -------------------------------------------------------------------------------- 1 | # Category Model 2 | class Category < ApplicationRecord 3 | has_many :tasks 4 | 5 | validates_presence_of :category 6 | end 7 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby.exe 2 | # frozen_string_literal: true 3 | 4 | require_relative '../config/boot' 5 | require 'rake' 6 | Rake.application.run 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /spec/support/request_spec_helper.rb: -------------------------------------------------------------------------------- 1 | module RequestSpecHelper 2 | # Parse JSON response to ruby hash 3 | def json 4 | JSON.parse(response.body) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | # Mailer Configuration 2 | class ApplicationMailer < ActionMailer::Base 3 | default from: 'from@example.com' 4 | layout 'mailer' 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/category_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Category, type: :model do 4 | it { should have_many(:tasks) } 5 | it { should validate_presence_of(:category) } 6 | end 7 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative 'application' 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby.exe 2 | # frozen_string_literal: true 3 | 4 | APP_PATH = File.expand_path('../config/application', __dir__) 5 | require_relative '../config/boot' 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Add new mime types for use in respond_to blocks: 5 | # Mime::Type.register "text/richtext", :rtf 6 | -------------------------------------------------------------------------------- /spec/factories/tasks.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :task do 3 | name { Faker::Lorem.word } 4 | description { Faker::Lorem.word } 5 | start { Time.now } 6 | category_id { 1 } 7 | user_id { 1 } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: WebApp_BackEnd_production 11 | -------------------------------------------------------------------------------- /spec/factories/users.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :user do 3 | sequence(:username) { |n| "user#{n}" } 4 | sequence(:full_name) { |n| "User#{n}" } 5 | email { "#{full_name}@example.com".downcase } 6 | password { 'password' } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 4 | 5 | require 'bundler/setup' # Set up gems listed in the Gemfile. 6 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations. 7 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Add your own tasks in files placed in lib/tasks ending in .rake, 4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 5 | 6 | require_relative 'config/application' 7 | 8 | Rails.application.load_tasks 9 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Configure sensitive parameters which will be filtered from the log file. 6 | Rails.application.config.filter_parameters += [:password] 7 | -------------------------------------------------------------------------------- /db/migrate/20200807232004_create_categories.rb: -------------------------------------------------------------------------------- 1 | # Generate Categories Migration 2 | class CreateCategories < ActiveRecord::Migration[6.0] 3 | def change 4 | create_table :categories do |t| 5 | t.string :category 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/models/token.rb: -------------------------------------------------------------------------------- 1 | class Token < ApplicationRecord 2 | belongs_to :user, -> { order('created_at desc') }, 3 | foreign_key: 'user_id', 4 | required: true 5 | 6 | validates :token, presence: true, uniqueness: true 7 | validates :request_ip, presence: true 8 | end 9 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # ActiveSupport::Reloader.to_prepare do 5 | # ApplicationController.renderer.defaults.merge!( 6 | # http_host: 'example.org', 7 | # https: false 8 | # ) 9 | # end 10 | -------------------------------------------------------------------------------- /test/channels/application_cable/connection_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase 4 | # test "connects with cookies" do 5 | # cookies.signed[:user_id] = 42 6 | # 7 | # connect 8 | # 9 | # assert_equal connection.user_id, "42" 10 | # end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200807230510_create_users.rb: -------------------------------------------------------------------------------- 1 | # Generate User Migration 2 | class CreateUsers < ActiveRecord::Migration[6.0] 3 | def change 4 | create_table :users do |t| 5 | t.string :full_name 6 | t.string :username 7 | t.string :email 8 | t.string :password_digest 9 | 10 | t.timestamps 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/models/task_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Task, type: :model do 4 | it { should belong_to(:category) } 5 | it { should belong_to(:user) } 6 | it { should validate_presence_of(:name) } 7 | it { should validate_length_of(:name).is_at_most(30) } 8 | it { should validate_length_of(:description).is_at_most(255) } 9 | end 10 | -------------------------------------------------------------------------------- /spec/models/token_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Token, type: :model do 4 | it { 5 | should belong_to(:user) 6 | .order('created_at desc') 7 | } 8 | it { should validate_presence_of(:token) } 9 | it { should validate_uniqueness_of(:token).ignoring_case_sensitivity } 10 | it { should validate_presence_of(:request_ip) } 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200815215500_create_tokens.rb: -------------------------------------------------------------------------------- 1 | # Generate Token Migration 2 | class CreateTokens < ActiveRecord::Migration[6.0] 3 | def change 4 | create_table :tokens do |t| 5 | t.string :token, index: { unique: true } 6 | t.string :request_ip 7 | 8 | t.references :user, index: true, foreign_key: true 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::API 2 | include ExceptionHandler 3 | 4 | # called before every action on controllers 5 | before_action :authorize_request 6 | attr_reader :current_user 7 | 8 | private 9 | 10 | # Check for valid request token and return user 11 | def authorize_request 12 | @current_user = AuthorizeApiRequest.new(request.headers, request.ip).call[:user] 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 5 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 6 | 7 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 8 | # Rails.backtrace_cleaner.remove_silencers! 9 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] ||= 'test' 2 | require_relative '../config/environment' 3 | require 'rails/test_help' 4 | 5 | # Test Helper 6 | class ActiveSupport::TestCase 7 | # Run tests in parallel with specified workers 8 | parallelize(workers: :number_of_processors, with: :threads) 9 | 10 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 11 | fixtures :all 12 | 13 | # Add more helper methods to be used by all tests here... 14 | end 15 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | # User Model 2 | class User < ApplicationRecord 3 | # encrypt password 4 | has_secure_password 5 | 6 | # Model associations 7 | has_many :tasks, foreign_key: :user_id 8 | has_many :tokens, dependent: :destroy 9 | 10 | # Validations 11 | validates :full_name, presence: true, length: { maximum: 50 } 12 | validates :username, presence: true, uniqueness: true, length: { maximum: 30 } 13 | validates :email, presence: true, uniqueness: true 14 | validates :password, presence: true, length: { maximum: 15, minimum: 6 } 15 | end 16 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 7 | # Character.create(name: 'Luke', movie: movies.first) 8 | 9 | Category.create( 10 | [ 11 | { category: 'working' }, 12 | { category: 'studing' }, 13 | { category: 'eating' }, 14 | { category: 'sleeping' } 15 | ] 16 | ) 17 | -------------------------------------------------------------------------------- /config/initializers/cors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Avoid CORS issues when API is called from the frontend app. 6 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. 7 | 8 | # Read more: https://github.com/cyu/rack-cors 9 | 10 | Rails.application.config.middleware.insert_before 0, Rack::Cors do 11 | allow do 12 | origins '*' 13 | 14 | resource '*', 15 | headers: :any, 16 | methods: %i[get post put patch delete options head] 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # This file contains settings for ActionController::ParamsWrapper which 6 | # is enabled by default. 7 | 8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 9 | ActiveSupport.on_load(:action_controller) do 10 | wrap_parameters format: [:json] 11 | end 12 | 13 | # To enable root element in JSON for ActiveRecord objects. 14 | # ActiveSupport.on_load(:active_record) do 15 | # self.include_root_in_json = true 16 | # end 17 | -------------------------------------------------------------------------------- /db/migrate/20200807232006_create_tasks.rb: -------------------------------------------------------------------------------- 1 | # Generate Task Migration 2 | class CreateTasks < ActiveRecord::Migration[6.0] 3 | def change 4 | create_table :tasks do |t| 5 | t.string :name 6 | t.string :description 7 | t.datetime :start 8 | t.datetime :end 9 | t.integer :seconds 10 | t.integer :minutes 11 | t.integer :hours 12 | 13 | t.references :category, references: :categories, foreign_key: { to_table: :categories } 14 | t.references :user, references: :users, foreign_key: { to_table: :users } 15 | 16 | t.timestamps 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | Pa7kQbaTN0QrOTSMkdo8FfhtZOS5WCbiXABoDnAWtuEjl6lXj/MKVofFb9I4Jld+w10BHbvkkYFONW6k4Z0FwDaBczES1VQxk/wlQMUFN+qG9YSEUvFasS9wo9Z1PHXsXrO7WR29l9PnMS6etKVDqRPav7xKzAMD12t/N7mVK5UeEDH2SbcLJQqEPvRx0lf93deBa2UIDrAYz0KcuDj7AP8URj2NWB67A6RioQR9THr7aJftvacA+TuoGAWTE62GFYUWtrL/QOV1cfvdvBirz0TDc56BWc/WtgMpmACK8vxPlca7OWDwFihwEWAeEZlmw+VpQDyGqQk5f66dIoZ5EgVgur1CIujCVCTZ7YPhK8hCkxX5TG/mhUzW5wPma/i3w54+3YVNNFWGyJ180t1hsA+6KTRP+09U7UzglHIHKuR05KotKqz2TWbzP9+V7X28GDRXmV1/wTLUlyaDRAX85x+9G/repB1r4ahHlYfJ9zMUNsXuEusAibZ8t2XVzYkvIGMrq2wbWew/TXzGhLa0qPjjYBhB1NP6xU2U0x7LkqIu58yPvzK8Sy8dk1I+/tD2bXFGCGO6We23Z0wrfia4inFTGLec--0MfDbBa4gUiESInV--+AGHbjYFhVKt9HQYpaRHDA== -------------------------------------------------------------------------------- /app/models/task.rb: -------------------------------------------------------------------------------- 1 | # Task Model 2 | class Task < ApplicationRecord 3 | belongs_to :category 4 | belongs_to :user 5 | 6 | before_save :task_create 7 | before_update :task_close 8 | 9 | validates :name, presence: true, length: { maximum: 30 } 10 | validates :description, length: { maximum: 255 } 11 | 12 | protected 13 | 14 | def task_create 15 | self.start = Time.now if start.nil? 16 | end 17 | 18 | def task_close 19 | return if start.nil? || self.end.nil? 20 | 21 | total = self.end - start 22 | self.minutes = (total / 60).floor % 60 23 | self.seconds = total.to_i % 60 24 | self.hours = ((total / 60).floor / 60) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.routes.draw do 4 | get '/api', to: 'api#index' 5 | 6 | namespace :api, defaults: { format: :json } do 7 | resources :tasks, except: %i[destroy show] 8 | get '/searcher', to: 'tasks#searcher' 9 | get '/searcher/by_category_date', to: 'tasks#search_by_category' 10 | 11 | namespace :auth do 12 | get '/', to: 'authentication#authorize' 13 | post '/login', to: 'authentication#authenticate' 14 | post '/signup', to: 'authentication#create' 15 | end 16 | end 17 | # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html 18 | end 19 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe User, type: :model do 4 | it { should have_many(:tasks) } 5 | it { 6 | should have_many(:tokens) 7 | .dependent(:destroy) 8 | } 9 | it { should validate_presence_of(:full_name) } 10 | it { should validate_length_of(:full_name).is_at_most(50) } 11 | it { should validate_presence_of(:username) } 12 | it { should validate_uniqueness_of(:username) } 13 | it { should validate_length_of(:username).is_at_most(30) } 14 | it { should validate_presence_of(:email) } 15 | it { should validate_uniqueness_of(:email) } 16 | it { should validate_presence_of(:password) } 17 | it { should validate_length_of(:password).is_at_least(6).is_at_most(15) } 18 | end 19 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Add new inflection rules using the following format. Inflections 5 | # are locale specific, and you may define rules for as many different 6 | # locales as you wish. All of these examples are active by default: 7 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 8 | # inflect.plural /^(ox)$/i, '\1en' 9 | # inflect.singular /^(ox)en/i, '\1' 10 | # inflect.irregular 'person', 'people' 11 | # inflect.uncountable %w( fish sheep ) 12 | # end 13 | 14 | # These inflection rules are supported but not enabled by default: 15 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 16 | # inflect.acronym 'RESTful' 17 | # end 18 | -------------------------------------------------------------------------------- /app/lib/json_web_token.rb: -------------------------------------------------------------------------------- 1 | # app/lib/json_web_token.rb 2 | class JsonWebToken 3 | # secret to encode and decode token 4 | JWT_SECRET = Rails.application.credentials.secret_key_jwt 5 | 6 | def self.encode(payload, exp = 24.hours.from_now) 7 | # set expiry to 24 hours from creation time 8 | payload[:exp] = exp.to_i 9 | # sign token with application secret 10 | JWT.encode(payload, JWT_SECRET) 11 | end 12 | 13 | def self.decode(token) 14 | # get payload; first index in decoded Array 15 | body = JWT.decode(token, JWT_SECRET)[0] 16 | HashWithIndifferentAccess.new body 17 | # rescue from all decode errors 18 | rescue JWT::DecodeError => e 19 | # raise custom error to be handled by custom handler 20 | raise ExceptionHandler::InvalidToken, e.message 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /.stickler.yml: -------------------------------------------------------------------------------- 1 | # add the linters you want stickler to use for this project 2 | linters: 3 | rubocop: 4 | display_cop_names: true 5 | # indicate where is the config file for stylelint 6 | config: './rubocop.yml' 7 | 8 | #stylelint: 9 | # indicate where is the config file for stylelint 10 | #config: 'stylelint.config.js' 11 | 12 | # add the files here you want to be ignored by stylelint 13 | files: 14 | ignore: 15 | - "bin/*" 16 | - "db/*" 17 | - "config/*" 18 | - "Guardfile" 19 | - "Rakefile" 20 | - "README.md" 21 | - "node_modules/**/*" 22 | 23 | # PLEASE DO NOT enable auto fixing options 24 | # if you need extra support from you linter - do it in your local env as described in README for this config 25 | 26 | # find full documentation here: https://stickler-ci.com/docs -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | /db/*.sqlite3-* 14 | 15 | # Ignore all logfiles and tempfiles. 16 | /log/* 17 | /tmp/* 18 | !/log/.keep 19 | !/tmp/.keep 20 | 21 | # Ignore pidfiles, but keep the directory. 22 | /tmp/pids/* 23 | !/tmp/pids/ 24 | !/tmp/pids/.keep 25 | 26 | # Ignore uploaded files in development. 27 | /storage/* 28 | !/storage/.keep 29 | .byebug_history 30 | 31 | # Ignore master key for decrypting credentials and more. 32 | #/config/master.key 33 | -------------------------------------------------------------------------------- /app/lib/message.rb: -------------------------------------------------------------------------------- 1 | # app/lib/message.rb 2 | class Message 3 | def self.not_found(record = 'record') 4 | "Sorry, #{record} not found." 5 | end 6 | 7 | def self.invalid_credentials 8 | 'Invalid credentials' 9 | end 10 | 11 | def self.invalid_token 12 | 'Invalid token' 13 | end 14 | 15 | def self.invalid_ip 16 | 'Invalid token' 17 | end 18 | 19 | def self.invalid_token_age 20 | 'Invalid token' 21 | end 22 | 23 | def self.missing_token 24 | 'Missing token' 25 | end 26 | 27 | def self.missing_id 28 | 'Missing token' 29 | end 30 | 31 | def self.unauthorized 32 | 'Unauthorized request' 33 | end 34 | 35 | def self.account_created 36 | 'Account created successfully' 37 | end 38 | 39 | def self.account_not_created 40 | 'Account could not be created' 41 | end 42 | 43 | def self.expired_token 44 | 'Sorry, your token has expired. Please login to continue.' 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby.exe 2 | # frozen_string_literal: true 3 | 4 | require 'fileutils' 5 | 6 | # path to your application root. 7 | APP_ROOT = File.expand_path('..', __dir__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | FileUtils.chdir APP_ROOT do 14 | # This script is a way to setup or update your development environment automatically. 15 | # This script is idempotent, so that you can run it at anytime and get an expectable outcome. 16 | # Add necessary setup steps to this file. 17 | 18 | puts '== Installing dependencies ==' 19 | system! 'gem install bundler --conservative' 20 | system('bundle check') || system!('bundle install') 21 | 22 | # puts "\n== Copying sample files ==" 23 | # unless File.exist?('config/database.yml') 24 | # FileUtils.cp 'config/database.yml.sample', 'config/database.yml' 25 | # end 26 | 27 | puts "\n== Preparing database ==" 28 | system! 'bin/rails db:prepare' 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 | -------------------------------------------------------------------------------- /app/controllers/api/auth/authentication_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | module Auth 3 | # Authentication Controller 4 | class AuthenticationController < ApplicationController 5 | skip_before_action :authorize_request, only: %i[authenticate create] 6 | def create 7 | user = User.create!(user_params) 8 | auth_token = AuthenticateUser.new(user.email, user.password, request.ip).call 9 | response = { message: Message.account_created, auth_token: auth_token } 10 | render json: response, status: :created 11 | end 12 | 13 | def authenticate 14 | auth_token = 15 | AuthenticateUser.new(auth_params[:login], auth_params[:password], request.ip).call 16 | render json: { auth_token: auth_token } 17 | end 18 | 19 | def authorize 20 | render json: nil, status: :ok 21 | end 22 | 23 | private 24 | 25 | def auth_params 26 | params.permit(:login, :password) 27 | end 28 | 29 | def user_params 30 | params.permit( 31 | :full_name, 32 | :username, 33 | :email, 34 | :password, 35 | :password_confirmation 36 | ) 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /app/auth/authenticate_user.rb: -------------------------------------------------------------------------------- 1 | # Authenticate User by JWT 2 | class AuthenticateUser 3 | def initialize(login, password, request_ip) 4 | @login = login 5 | @password = password 6 | @request_ip = request_ip 7 | end 8 | 9 | # Service entry point 10 | def call 11 | return unless user 12 | 13 | access_token = set_access_token 14 | JsonWebToken.encode( 15 | token: access_token.token, 16 | request_ip: access_token.request_ip 17 | ) 18 | end 19 | 20 | private 21 | 22 | attr_reader :email, :password, :request_ip 23 | 24 | # verify user credentials 25 | def find_user 26 | user = User.find_by(email: @login) 27 | return user if user 28 | 29 | User.find_by(username: @login) 30 | end 31 | 32 | def user 33 | user = find_user 34 | return user if user&.authenticate(password) 35 | 36 | # raise Authentication error if credentials are invalid 37 | raise(ExceptionHandler::AuthenticationError, Message.invalid_credentials) 38 | end 39 | 40 | def set_access_token 41 | user.tokens.create(token: token_creator, request_ip: @request_ip) 42 | end 43 | 44 | def token_creator 45 | loop do 46 | token = SecureRandom.hex(20) 47 | break token unless Token.where(token: token).exists? 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/auth/authenticate_user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe AuthenticateUser do 4 | # create test user 5 | let(:user) { create(:user) } 6 | # valid request subject 7 | subject(:valid_auth_obj_by_mail) { described_class.new(user.email, user.password, '127.0.0.1') } 8 | subject(:valid_auth_obj_by_user) { described_class.new(user.username, user.password, '127.0.0.1') } 9 | # invalid request subject 10 | subject(:invalid_auth_obj) { described_class.new('foo', 'bar', '') } 11 | 12 | # Test suite for AuthenticateUser#call 13 | describe '#call' do 14 | # return token when valid request 15 | context 'when valid credentials' do 16 | it 'returns an auth token when email is given' do 17 | token = valid_auth_obj_by_mail.call 18 | expect(token).not_to be_nil 19 | end 20 | 21 | it 'returns an auth token when user is given' do 22 | token = valid_auth_obj_by_user.call 23 | expect(token).not_to be_nil 24 | end 25 | end 26 | 27 | # raise Authentication Error when invalid request 28 | context 'when invalid credentials' do 29 | it 'raises an authentication error' do 30 | expect { invalid_auth_obj.call } 31 | .to raise_error( 32 | ExceptionHandler::AuthenticationError, 33 | /Invalid credentials/ 34 | ) 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/lib/exception_handler.rb: -------------------------------------------------------------------------------- 1 | # Handle Auth Exception 2 | module ExceptionHandler 3 | extend ActiveSupport::Concern 4 | 5 | # Define custom error subclasses - rescue catches `StandardErrors` 6 | class AuthenticationError < StandardError; end 7 | class MissingToken < StandardError; end 8 | class InvalidToken < StandardError; end 9 | class InvalidIp < StandardError; end 10 | class InvalidTokenAge < StandardError; end 11 | 12 | included do 13 | # Define custom handlers 14 | rescue_from ActiveRecord::RecordInvalid, with: :four_twenty_two 15 | rescue_from ExceptionHandler::AuthenticationError, with: :unauthorized_request 16 | rescue_from ExceptionHandler::MissingToken, with: :four_twenty_two 17 | rescue_from ExceptionHandler::InvalidToken, with: :four_twenty_two 18 | rescue_from ExceptionHandler::InvalidIp, with: :four_twenty_two 19 | rescue_from ExceptionHandler::InvalidTokenAge, with: :four_twenty_two 20 | 21 | rescue_from ActiveRecord::RecordNotFound do |e| 22 | render json: { message: e.message }, status: :not_found 23 | end 24 | end 25 | 26 | private 27 | 28 | # JSON response with message; Status code 422 - unprocessable entity 29 | def four_twenty_two(error_msg) 30 | render json: { message: error_msg.message }, status: :unprocessable_entity 31 | end 32 | 33 | # JSON response with message; Status code 401 - Unauthorized 34 | def unauthorized_request(error_msg) 35 | render json: { message: error_msg.message }, status: :unauthorized 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'boot' 4 | 5 | require 'rails' 6 | # Pick the frameworks you want: 7 | require 'active_model/railtie' 8 | require 'active_job/railtie' 9 | require 'active_record/railtie' 10 | require 'active_storage/engine' 11 | require 'action_controller/railtie' 12 | require 'action_mailer/railtie' 13 | require 'action_mailbox/engine' 14 | require 'action_text/engine' 15 | require 'action_view/railtie' 16 | require 'action_cable/engine' 17 | # require "sprockets/railtie" 18 | require 'rails/test_unit/railtie' 19 | 20 | # Require the gems listed in Gemfile, including any gems 21 | # you've limited to :test, :development, or :production. 22 | Bundler.require(*Rails.groups) 23 | 24 | module WebAppBackend 25 | # Application Configuration 26 | class Application < Rails::Application 27 | # Initialize configuration defaults for originally generated Rails version. 28 | config.load_defaults 6.0 29 | 30 | # Settings in config/environments/* take precedence over those specified here. 31 | # Application configuration can go into files in config/initializers 32 | # -- all .rb files in that directory are automatically loaded after loading 33 | # the framework and any gems in your application. 34 | 35 | # Only loads a smaller set of middleware suitable for API only apps. 36 | # Middleware like session, flash, cookies can be added back manually. 37 | # Skip views, helpers and assets when generating a new resource. 38 | config.api_only = true 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/controllers/application_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe ApplicationController, type: :controller do 4 | # create test user 5 | let!(:user) { create(:user) } 6 | # set headers for authorization 7 | let(:invalid_headers_by_ip) { { 'Authorization' => token_generator(user.id, '1.1.1.1') } } 8 | let(:headers) { { 'Authorization' => token_generator(user.id, '0.0.0.0') } } 9 | let(:invalid_headers) { { 'Authorization' => nil } } 10 | 11 | describe '#authorize_request' do 12 | context 'when auth token is passed' do 13 | before { allow(request).to receive(:headers).and_return(headers) } 14 | 15 | # private method authorize_request returns current user 16 | it 'sets the current user' do 17 | expect(subject.instance_eval { authorize_request }).to eq(user) 18 | end 19 | end 20 | 21 | context 'when auth token is not passed' do 22 | before do 23 | allow(request).to receive(:headers).and_return(invalid_headers) 24 | end 25 | 26 | it 'raises MissingToken error' do 27 | expect { subject.instance_eval { authorize_request } } 28 | .to raise_error(ExceptionHandler::MissingToken, /Missing token/) 29 | end 30 | end 31 | 32 | context 'when auth token has wrong IP' do 33 | before do 34 | allow(request).to receive(:headers).and_return(invalid_headers_by_ip) 35 | end 36 | 37 | it 'raises InvalidIp error' do 38 | expect { subject.instance_eval { authorize_request } } 39 | .to raise_error(ExceptionHandler::InvalidIp, /Invalid token/) 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/support/controller_spec_helper.rb: -------------------------------------------------------------------------------- 1 | # spec/support/controller_spec_helper.rb 2 | module ControllerSpecHelper 3 | # Generate Token 4 | def token_creator 5 | loop do 6 | token = SecureRandom.hex(20) 7 | break token unless Token.where(token: token).exists? 8 | end 9 | end 10 | 11 | # Grant Access from user id 12 | def token_generator(user_id, user_ip) 13 | return unless user_id 14 | 15 | user = User.find(user_id) 16 | 17 | access_token = user.tokens.create(token: token_creator, request_ip: user_ip) 18 | JsonWebToken.encode( 19 | token: access_token.token, 20 | request_ip: access_token.request_ip 21 | ) 22 | end 23 | 24 | def invalid_token_generator(_user_id, user_ip) 25 | JsonWebToken.encode( 26 | token: token_creator, 27 | request_ip: user_ip 28 | ) 29 | end 30 | 31 | # generate expired tokens from user id 32 | def expired_token_generator(user_id, user_ip) 33 | user = User.find(user_id) 34 | 35 | access_token = user.tokens.create(token: token_creator, request_ip: user_ip) 36 | JsonWebToken.encode( 37 | { 38 | token: access_token.token, 39 | request_ip: access_token.request_ip 40 | }, (Time.now.to_i - 10) 41 | ) 42 | end 43 | 44 | # return valid headers 45 | def valid_headers 46 | { 47 | 'Authorization' => token_generator(user.id, '127.0.0.1'), 48 | 'Content-Type' => 'application/json' 49 | } 50 | end 51 | 52 | # return invalid headers 53 | def invalid_headers 54 | { 55 | 'Authorization' => nil, 56 | 'Content-Type' => 'application/json' 57 | } 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Puma can serve each request in a thread from an internal thread pool. 4 | # The `threads` method setting takes two numbers: a minimum and maximum. 5 | # Any libraries that use thread pools should be configured to match 6 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 7 | # and maximum; this matches the default thread size of Active Record. 8 | # 9 | max_threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 } 10 | min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count } 11 | threads min_threads_count, max_threads_count 12 | 13 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 14 | # 15 | port ENV.fetch('PORT') { 3000 } 16 | 17 | # Specifies the `environment` that Puma will run in. 18 | # 19 | environment ENV.fetch('RAILS_ENV') { 'development' } 20 | 21 | # Specifies the `pidfile` that Puma will use. 22 | pidfile ENV.fetch('PIDFILE') { 'tmp/pids/server.pid' } 23 | 24 | # Specifies the number of `workers` to boot in clustered mode. 25 | # Workers are forked web server processes. If using threads and workers together 26 | # the concurrency of the application would be max `threads` * `workers`. 27 | # Workers do not work on JRuby or Windows (both of which do not support 28 | # processes). 29 | # 30 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 31 | 32 | # Use the `preload_app!` method when specifying a `workers` number. 33 | # This directive tells Puma to first boot the application and load code 34 | # before forking the application. This takes advantage of Copy On Write 35 | # process behavior so workers use less memory. 36 | # 37 | # preload_app! 38 | 39 | # Allow puma to be restarted by `rails restart` command. 40 | plugin :tmp_restart 41 | -------------------------------------------------------------------------------- /app/controllers/api/tasks_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | # Api Task Controller 3 | class TasksController < ApplicationController 4 | def index 5 | tasks = current_user.tasks.order('start DESC').all 6 | remove_values = %i[created_at updated_at user_id] 7 | render json: tasks.except(remove_values), status: :ok 8 | end 9 | 10 | def create 11 | search_active = current_user.tasks.where(end: nil).first 12 | task = current_user.tasks.new(permitted_create_params) 13 | task.save if search_active.nil? 14 | render json: nil, status: :ok 15 | end 16 | 17 | def update 18 | task = current_user.tasks.find(params[:id]) 19 | task.end = Time.at(permitted_update_params[:end].to_i / 1000) 20 | task.save 21 | end 22 | 23 | def search_by_category 24 | tasks = current_user.tasks.where(search_params_scope) 25 | remove_values = %i[created_at updated_at user_id] 26 | render json: tasks.except(remove_values), status: :ok 27 | end 28 | 29 | def searcher 30 | tasks = current_user.tasks.where(end: nil).first 31 | render json: tasks, status: :ok 32 | end 33 | 34 | private 35 | 36 | def permitted_update_params 37 | att_update = %i[end] 38 | params.require(:task).permit(att_update) 39 | end 40 | 41 | def search_params_scope 42 | range = case params[:range].to_i 43 | when 7 then Date.today.all_week 44 | when 30 then Date.today.all_month 45 | else Time.now.all_day 46 | end 47 | { start: range, category_id: params[:category_id] } 48 | end 49 | 50 | def permitted_create_params 51 | att_create = %i[name description category_id] 52 | params.require(:tasks).permit(att_create) 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /app/auth/authorize_api_request.rb: -------------------------------------------------------------------------------- 1 | # Api Authentication Controller 2 | class AuthorizeApiRequest 3 | def initialize(headers = {}, request_ip = '') 4 | @headers = headers 5 | @request_ip = request_ip 6 | end 7 | 8 | # Service entry point - return valid user object 9 | def call 10 | { 11 | user: user_authentication 12 | } 13 | end 14 | 15 | private 16 | 17 | attr_reader :headers, :request_ip, :current_user, :decoded_token 18 | 19 | def user_authentication 20 | # check if user_ip = request_ip 21 | 22 | # check if user is in the database 23 | # memoize user object 24 | 25 | @current_user ||= auth_token.user if auth_token 26 | raise(ExceptionHandler::InvalidTokenAge, Message.invalid_token_age) unless token_valid? 27 | 28 | current_user 29 | # handle user not found 30 | rescue ActiveRecord::RecordNotFound => e 31 | # raise custom error 32 | raise( 33 | ExceptionHandler::InvalidToken, 34 | ("#{Message.invalid_token} #{e.message}") 35 | ) 36 | end 37 | 38 | def token_valid? 39 | current_user.tokens.order('created_at desc').first.token == decoded_token 40 | end 41 | 42 | def auth_token 43 | @decoded_token = decoded_auth_token[:token] 44 | token = Token.find_by_token(decoded_token) 45 | 46 | raise(ExceptionHandler::InvalidToken, Message.invalid_token) unless token 47 | 48 | raise(ExceptionHandler::InvalidIp, Message.invalid_ip) if token.request_ip != request_ip 49 | 50 | token 51 | end 52 | 53 | # decode authentication token 54 | def decoded_auth_token 55 | @decoded_auth_token ||= JsonWebToken.decode(http_auth_header) 56 | end 57 | 58 | # check for token in `Authorization` header 59 | def http_auth_header 60 | return headers['Authorization'].split(' ').last if headers['Authorization'].present? 61 | 62 | raise(ExceptionHandler::MissingToken, Message.missing_token) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded on 7 | # every request. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable/disable caching. By default caching is disabled. 18 | # Run rails dev:cache to toggle caching. 19 | if Rails.root.join('tmp', 'caching-dev.txt').exist? 20 | config.cache_store = :memory_store 21 | config.public_file_server.headers = { 22 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 23 | } 24 | else 25 | config.action_controller.perform_caching = false 26 | 27 | config.cache_store = :null_store 28 | end 29 | 30 | # Store uploaded files on the local file system (see config/storage.yml for options). 31 | config.active_storage.service = :local 32 | 33 | # Don't care if the mailer can't send. 34 | config.action_mailer.raise_delivery_errors = false 35 | 36 | config.action_mailer.perform_caching = false 37 | 38 | # Print deprecation notices to the Rails logger. 39 | config.active_support.deprecation = :log 40 | 41 | # Raise an error on page load if there are pending migrations. 42 | config.active_record.migration_error = :page_load 43 | 44 | # Highlight code that triggered database queries in logs. 45 | config.active_record.verbose_query_logs = true 46 | 47 | # Raises error for missing translations. 48 | # config.action_view.raise_on_missing_translations = true 49 | 50 | # Use an evented file watcher to asynchronously detect changes in source code, 51 | # routes, locales, etc. This feature depends on the listen gem. 52 | # config.file_watcher = ActiveSupport::EventedFileUpdateChecker 53 | end 54 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | config.cache_classes = true 12 | 13 | # Do not eager load code on boot. This avoids loading your whole application 14 | # just for the purpose of running a single test. If you are using a tool that 15 | # preloads Rails for running tests, you may have to set it to true. 16 | config.eager_load = false 17 | 18 | # Configure public file server for tests with Cache-Control for performance. 19 | config.public_file_server.enabled = true 20 | config.public_file_server.headers = { 21 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}" 22 | } 23 | 24 | # Show full error reports and disable caching. 25 | config.consider_all_requests_local = true 26 | config.action_controller.perform_caching = false 27 | config.cache_store = :null_store 28 | 29 | # Raise exceptions instead of rendering exception templates. 30 | config.action_dispatch.show_exceptions = false 31 | 32 | # Disable request forgery protection in test environment. 33 | config.action_controller.allow_forgery_protection = false 34 | 35 | # Store uploaded files on the local file system in a temporary directory. 36 | config.active_storage.service = :test 37 | 38 | config.action_mailer.perform_caching = false 39 | 40 | # Tell Action Mailer not to deliver emails to the real world. 41 | # The :test delivery method accumulates sent emails in the 42 | # ActionMailer::Base.deliveries array. 43 | config.action_mailer.delivery_method = :test 44 | 45 | # Print deprecation notices to the stderr. 46 | config.active_support.deprecation = :stderr 47 | 48 | # Raises error for missing translations. 49 | # config.action_view.raise_on_missing_translations = true 50 | end 51 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby('2.7.1') 5 | 6 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 7 | gem('rails', '~> 6.0.3', '>= 6.0.3.2') 8 | # Use sqlite3 as the database for Active Record 9 | gem('puma') 10 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 11 | # gem 'jbuilder', '~> 2.7' 12 | # Use Redis adapter to run Action Cable in production 13 | # gem 'redis', '~> 4.0' 14 | # Use Active Model has_secure_password 15 | gem('bcrypt') 16 | # Use Active Storage variant 17 | # gem 'image_processing', '~> 1.2' 18 | 19 | # Reduces boot times through caching; required in config/boot.rb 20 | gem('bootsnap', require: false) 21 | 22 | # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible 23 | gem('rack-cors') 24 | # Add JSON Token Auth 25 | gem('jwt') 26 | 27 | group :development, :test do 28 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 29 | gem('byebug', platforms: %i[mri mingw x64_mingw]) 30 | # Use sqlite3 as the database for Active Record 31 | gem('sqlite3') 32 | # Test Rails 33 | gem('rspec-rails') 34 | end 35 | 36 | group :development do 37 | gem('foreman') 38 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 39 | gem('web-console') 40 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 41 | gem('listen') 42 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 43 | gem('spring') 44 | # Extention of Spring 45 | gem('spring-watcher-listen') 46 | end 47 | 48 | group :test do 49 | # Adds support for Capybara system testing and selenium driver 50 | gem('capybara') 51 | # Easy installation and use of web drivers to run system tests with browsers 52 | gem('webdrivers') 53 | # Help Populate Test Database 54 | gem('database_cleaner') 55 | gem('factory_bot_rails') 56 | gem('faker') 57 | gem('shoulda-matchers') 58 | end 59 | 60 | group :production do 61 | # Add a Postgrade server 62 | gem('pg') 63 | end 64 | 65 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 66 | gem('tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]) 67 | -------------------------------------------------------------------------------- /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 | # This file is the source Rails uses to define your schema when running `rails 6 | # db:schema:load`. When creating a new database, `rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 2020_08_15_215500) do 14 | 15 | create_table "categories", force: :cascade do |t| 16 | t.string "category" 17 | t.datetime "created_at", precision: 6, null: false 18 | t.datetime "updated_at", precision: 6, null: false 19 | end 20 | 21 | create_table "tasks", force: :cascade do |t| 22 | t.string "name" 23 | t.string "description" 24 | t.datetime "start" 25 | t.datetime "end" 26 | t.integer "seconds" 27 | t.integer "minutes" 28 | t.integer "hours" 29 | t.integer "category_id" 30 | t.integer "user_id" 31 | t.datetime "created_at", precision: 6, null: false 32 | t.datetime "updated_at", precision: 6, null: false 33 | t.index ["category_id"], name: "index_tasks_on_category_id" 34 | t.index ["user_id"], name: "index_tasks_on_user_id" 35 | end 36 | 37 | create_table "tokens", force: :cascade do |t| 38 | t.string "token" 39 | t.string "request_ip" 40 | t.integer "user_id" 41 | t.datetime "created_at", precision: 6, null: false 42 | t.datetime "updated_at", precision: 6, null: false 43 | t.index ["token"], name: "index_tokens_on_token", unique: true 44 | t.index ["user_id"], name: "index_tokens_on_user_id" 45 | end 46 | 47 | create_table "users", force: :cascade do |t| 48 | t.string "full_name" 49 | t.string "username" 50 | t.string "email" 51 | t.string "password_digest" 52 | t.datetime "created_at", precision: 6, null: false 53 | t.datetime "updated_at", precision: 6, null: false 54 | end 55 | 56 | add_foreign_key "tasks", "categories" 57 | add_foreign_key "tasks", "users" 58 | add_foreign_key "tokens", "users" 59 | end 60 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - "db/schema.rb" 4 | - "bin/*" 5 | - "config/**/*" 6 | - "Guardfile" 7 | - "Rakefile" 8 | - "README.md" 9 | - "node_modules/**/*" 10 | - "client/node_modules/**/*" 11 | 12 | DisplayCopNames: true 13 | 14 | Layout/LineLength: 15 | Max: 125 16 | Metrics/MethodLength: 17 | Include: 18 | - "app/controllers/*" 19 | - "app/models/*" 20 | Max: 20 21 | Metrics/AbcSize: 22 | Include: 23 | - "app/controllers/*" 24 | - "app/models/*" 25 | Max: 50 26 | Metrics/ClassLength: 27 | Max: 150 28 | Metrics/BlockLength: 29 | ExcludedMethods: ['describe'] 30 | Max: 30 31 | Layout/HashAlignment: 32 | EnforcedColonStyle: key 33 | Layout/ExtraSpacing: 34 | AllowForAlignment: false 35 | Layout/MultilineMethodCallIndentation: 36 | Enabled: true 37 | EnforcedStyle: indented 38 | Layout/EmptyLinesAroundAttributeAccessor: 39 | Enabled: true 40 | Layout/SpaceAroundMethodCallOperator: 41 | Enabled: true 42 | Lint/DeprecatedOpenSSLConstant: 43 | Enabled: true 44 | Lint/RaiseException: 45 | Enabled: true 46 | Lint/StructNewOverride: 47 | Enabled: true 48 | Lint/BinaryOperatorWithIdenticalOperands: 49 | Enabled: true 50 | Lint/DuplicateElsifCondition: 51 | Enabled: true 52 | Lint/DuplicateRescueException: 53 | Enabled: true 54 | Lint/EmptyConditionalBody: 55 | Enabled: true 56 | Lint/FloatComparison: 57 | Enabled: true 58 | Lint/MissingSuper: 59 | Enabled: true 60 | Lint/MixedRegexpCaptureTypes: 61 | Enabled: true 62 | Lint/OutOfRangeRegexpRef: 63 | Enabled: true 64 | Lint/SelfAssignment: 65 | Enabled: true 66 | Lint/TopLevelReturnWithArgument: 67 | Enabled: true 68 | Lint/UnreachableLoop: 69 | Enabled: true 70 | Style/ExponentialNotation: 71 | Enabled: true 72 | Style/HashEachMethods: 73 | Enabled: true 74 | Style/HashTransformKeys: 75 | Enabled: true 76 | Style/HashTransformValues: 77 | Enabled: true 78 | Style/SlicingWithRange: 79 | Enabled: true 80 | Style/AccessorGrouping: 81 | Enabled: true 82 | Style/ArrayCoercion: 83 | Enabled: true 84 | Style/BisectedAttrAccessor: 85 | Enabled: true 86 | Style/CaseLikeIf: 87 | Enabled: true 88 | Style/ExplicitBlockArgument: 89 | Enabled: true 90 | Style/GlobalStdStream: 91 | Enabled: true 92 | Style/HashAsLastArrayItem: 93 | Enabled: true 94 | Style/HashLikeCase: 95 | Enabled: true 96 | Style/OptionalBooleanParameter: 97 | Enabled: true 98 | Style/RedundantAssignment: 99 | Enabled: true 100 | Style/RedundantFetchBlock: 101 | Enabled: true 102 | Style/RedundantFileExtensionInRequire: 103 | Enabled: true 104 | Style/RedundantRegexpCharacterClass: 105 | Enabled: true 106 | Style/RedundantRegexpEscape: 107 | Enabled: true 108 | Style/SingleArgumentDig: 109 | Enabled: true 110 | Style/StringConcatenation: 111 | Enabled: true 112 | Style/Documentation: 113 | Enabled: false 114 | Style/ClassAndModuleChildren: 115 | Enabled: false 116 | Style/EachForSimpleLoop: 117 | Enabled: false 118 | Style/AndOr: 119 | Enabled: false 120 | Style/DefWithParentheses: 121 | Enabled: false 122 | Style/FrozenStringLiteralComment: 123 | EnforcedStyle: never -------------------------------------------------------------------------------- /spec/requests/api/auth/authentication_request_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe 'Authentication', type: :request do 4 | # Authentication test suite 5 | describe 'POST /api/auth/login' do 6 | # create test user 7 | let!(:user) { create(:user) } 8 | # set headers for authorization 9 | let(:headers) { valid_headers.except('Authorization') } 10 | # set test valid and invalid credentials 11 | let(:valid_credentials_by_email) do 12 | { 13 | login: user.email, 14 | password: user.password 15 | }.to_json 16 | end 17 | let(:valid_credentials_by_username) do 18 | { 19 | login: user.username, 20 | password: user.password 21 | }.to_json 22 | end 23 | let(:invalid_credentials) do 24 | { 25 | login: Faker::Internet.email, 26 | password: Faker::Internet.password 27 | }.to_json 28 | end 29 | 30 | # set request.headers to our custon headers 31 | # before { allow(request).to receive(:headers).and_return(headers) } 32 | 33 | # returns auth token when request is valid 34 | context 'When request is valid' do 35 | before { post '/api/auth/login', params: valid_credentials_by_email, headers: headers } 36 | it 'returns an authentication token for a email validation' do 37 | expect(json['auth_token']).not_to be_nil 38 | end 39 | end 40 | 41 | context 'When request is valid' do 42 | before { post '/api/auth/login', params: valid_credentials_by_username, headers: headers } 43 | 44 | it 'returns an authentication token for a username validation' do 45 | expect(json['auth_token']).not_to be_nil 46 | end 47 | end 48 | 49 | # returns failure message when request is invalid 50 | context 'When request is invalid' do 51 | before { post '/api/auth/login', params: invalid_credentials, headers: headers } 52 | it 'returns a failure message' do 53 | expect(json['message']).to match(/Invalid credentials/) 54 | end 55 | end 56 | end 57 | 58 | describe 'POST /api/auth/signup' do 59 | let(:user) { build(:user) } 60 | let(:headers) { valid_headers.except('Authorization') } 61 | let(:valid_attributes) do 62 | attributes_for(:user, password_confirmation: user.password) 63 | end 64 | context 'when valid request' do 65 | before { post '/api/auth/signup', params: valid_attributes.to_json, headers: headers } 66 | 67 | it 'creates a new user' do 68 | expect(response).to have_http_status(201) 69 | end 70 | 71 | it 'returns success message' do 72 | expect(json['message']).to match(/Account created successfully/) 73 | end 74 | 75 | it 'returns an authentication token' do 76 | expect(json['auth_token']).not_to be_nil 77 | end 78 | end 79 | 80 | context 'when invalid request' do 81 | before { post '/api/auth/signup', params: {}, headers: headers } 82 | 83 | it 'does not create a new user' do 84 | expect(response).to have_http_status(422) 85 | end 86 | 87 | # rubocop: disable Layout/LineLength 88 | it 'returns failure message' do 89 | expect(json['message']) 90 | .to match("Validation failed: Password can't be blank, Password is too short (minimum is 6 characters), Full name can't be blank, Username can't be blank, Email can't be blank") 91 | end 92 | end 93 | # rubocop: enable Layout/LineLength 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | # rubocop: disable 12 | 13 | require 'rubygems' 14 | 15 | m = Module.new do 16 | module_function 17 | 18 | def invoked_as_script? 19 | File.expand_path($PROGRAM_NAME) == File.expand_path(__FILE__) 20 | end 21 | 22 | def env_var_version 23 | ENV['BUNDLER_VERSION'] 24 | end 25 | 26 | def cli_arg_version 27 | return unless invoked_as_script? # don't want to hijack other binstubs 28 | return unless 'update'.start_with?(ARGV.first || ' ') # must be running `bundle update` 29 | 30 | bundler_version = nil 31 | update_index = nil 32 | ARGV.each_with_index do |a, i| 33 | bundler_version = a if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 34 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 35 | 36 | bundler_version = Regexp.last_match(1) 37 | update_index = i 38 | end 39 | bundler_version 40 | end 41 | 42 | def gemfile 43 | gemfile = ENV['BUNDLE_GEMFILE'] 44 | return gemfile if gemfile && !gemfile.empty? 45 | 46 | File.expand_path('../Gemfile', __dir__) 47 | end 48 | 49 | def lockfile 50 | lockfile = 51 | case File.basename(gemfile) 52 | when 'gems.rb' then gemfile.sub(/\.rb$/, gemfile) 53 | else "#{gemfile}.lock" 54 | end 55 | File.expand_path(lockfile) 56 | end 57 | 58 | def lockfile_version 59 | return unless File.file?(lockfile) 60 | 61 | lockfile_contents = File.read(lockfile) 62 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 63 | 64 | Regexp.last_match(1) 65 | end 66 | 67 | def bundler_version 68 | @bundler_version ||= 69 | env_var_version || cli_arg_version || 70 | lockfile_version 71 | end 72 | 73 | def bundler_requirement 74 | return "#{Gem::Requirement.default}.a" unless bundler_version 75 | 76 | bundler_gem_version = Gem::Version.new(bundler_version) 77 | 78 | requirement = bundler_gem_version.approximate_recommendation 79 | 80 | return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new('2.7.0') 81 | 82 | requirement += '.a' if bundler_gem_version.prerelease? 83 | 84 | requirement 85 | end 86 | 87 | def load_bundler! 88 | ENV['BUNDLE_GEMFILE'] ||= gemfile 89 | 90 | activate_bundler 91 | end 92 | 93 | def activate_bundler 94 | gem_error = activation_error_handling do 95 | gem 'bundler', bundler_requirement 96 | end 97 | return if gem_error.nil? 98 | 99 | require_error = activation_error_handling do 100 | require 'bundler/version' 101 | end 102 | if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 103 | return 104 | end 105 | 106 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 107 | exit 42 108 | end 109 | 110 | def activation_error_handling 111 | yield 112 | nil 113 | rescue StandardError, LoadError => e 114 | e 115 | end 116 | end 117 | 118 | m.load_bundler! 119 | 120 | load Gem.bin_path('bundler', 'bundle') if m.invoked_as_script? 121 | # rubocop: enable 122 | -------------------------------------------------------------------------------- /spec/auth/authorize_api_request_spec.rb: -------------------------------------------------------------------------------- 1 | # rubocop: disable Metrics/BlockLength 2 | require 'rails_helper' 3 | 4 | RSpec.describe AuthorizeApiRequest, type: :request do 5 | # Create test user 6 | let(:user) { create(:user) } 7 | # Mock `Authorization` header 8 | let(:header) { { 'Authorization' => token_generator(user.id, '127.0.0.1') } } 9 | # Invalid request subject 10 | subject(:invalid_request_obj) { described_class.new({}, '') } 11 | # Valid request subject 12 | subject(:request_obj) { described_class.new(header, '127.0.0.1') } 13 | 14 | # Test Suite for AuthorizeApiRequest#call 15 | # This is our entry point into the service class 16 | describe '#call' do 17 | # returns user object when request is valid 18 | context 'when valid request' do 19 | it 'returns user object' do 20 | result = request_obj.call 21 | expect(result[:user]).to eq(user) 22 | end 23 | end 24 | 25 | # returns error message when invalid request 26 | context 'when invalid request' do 27 | context 'when missing token' do 28 | it 'raises a MissingToken error' do 29 | expect { invalid_request_obj.call } 30 | .to raise_error(ExceptionHandler::MissingToken, 'Missing token') 31 | end 32 | end 33 | 34 | context 'when invalid token by user_id' do 35 | subject(:invalid_request_obj) do 36 | # custom helper method `token_generator` 37 | described_class.new('Authorization' => invalid_token_generator(5, '12')) 38 | end 39 | 40 | it 'raises an InvalidToken error' do 41 | expect { invalid_request_obj.call } 42 | .to raise_error(ExceptionHandler::InvalidToken, /Invalid token/) 43 | end 44 | end 45 | 46 | context 'when invalid token by request_ip' do 47 | subject(:invalid_request_obj) do 48 | # custom helper method `token_generator` 49 | described_class.new('Authorization' => token_generator(user.id, '1')) 50 | end 51 | 52 | it 'raises an InvalidToken error' do 53 | expect { invalid_request_obj.call } 54 | .to raise_error(ExceptionHandler::InvalidIp, /Invalid token/) 55 | end 56 | end 57 | 58 | context 'when invalid token by old token' do 59 | # custom helper method `token_generator` 60 | let(:header_new) { { 'Authorization' => token_generator(user.id, '127.0.0.1') } } 61 | let(:header) { { 'Authorization' => token_generator(user.id, '127.0.0.123') } } 62 | subject(:request_obj) { described_class.new(header, '127.0.0.123') } 63 | subject(:request_obj_new) { described_class.new(header_new, '127.0.0.1') } 64 | 65 | it 'raises an InvalidToken error' do 66 | request_obj.call 67 | request_obj_new.call 68 | expect { request_obj.call } 69 | .to raise_error(ExceptionHandler::InvalidTokenAge, /Invalid token/) 70 | end 71 | end 72 | 73 | context 'when token is expired' do 74 | let(:header) { { 'Authorization' => expired_token_generator(user.id, '1') } } 75 | subject(:request_obj) { described_class.new(header) } 76 | 77 | it 'raises ExceptionHandler::ExpiredSignature error' do 78 | expect { request_obj.call } 79 | .to raise_error( 80 | ExceptionHandler::InvalidToken, 81 | /Signature has expired/ 82 | ) 83 | end 84 | end 85 | 86 | context 'fake token' do 87 | let(:header) { { 'Authorization' => 'foobar' } } 88 | subject(:invalid_request_obj) { described_class.new(header) } 89 | 90 | it 'handles JWT::DecodeError' do 91 | expect { invalid_request_obj.call } 92 | .to raise_error( 93 | ExceptionHandler::InvalidToken, 94 | /Not enough or too many segments/ 95 | ) 96 | end 97 | end 98 | end 99 | end 100 | end 101 | # rubocop: enable Metrics/BlockLength 102 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | require 'spec_helper' 3 | ENV['RAILS_ENV'] ||= 'test' 4 | require File.expand_path('../config/environment', __dir__) 5 | # Prevent database truncation if the environment is production 6 | abort('The Rails environment is running in production mode!') if Rails.env.production? 7 | require 'rspec/rails' 8 | # Add additional requires below this line. Rails is not loaded until this point! 9 | require 'database_cleaner' 10 | Dir[Rails.root.join('spec/support/**/*.rb')].sort.each { |f| require f } 11 | # configure shoulda matchers to use rspec as the test framework and full matcher libraries for rails 12 | Shoulda::Matchers.configure do |config| 13 | config.integrate do |with| 14 | with.test_framework :rspec 15 | with.library :rails 16 | end 17 | end 18 | 19 | # Requires supporting ruby files with custom matchers and macros, etc, in 20 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 21 | # run as spec files by default. This means that files in spec/support that end 22 | # in _spec.rb will both be required and run as specs, causing the specs to be 23 | # run twice. It is recommended that you do not name files matching this glob to 24 | # end with _spec.rb. You can configure this pattern with the --pattern 25 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 26 | # 27 | # The following line is provided for convenience purposes. It has the downside 28 | # of increasing the boot-up time by auto-requiring all files in the support 29 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 30 | # require only the support files necessary. 31 | # 32 | # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } 33 | 34 | # Checks for pending migrations and applies them before tests are run. 35 | # If you are not using ActiveRecord, you can remove these lines. 36 | begin 37 | ActiveRecord::Migration.maintain_test_schema! 38 | rescue ActiveRecord::PendingMigrationError => e 39 | puts e.to_s.strip 40 | exit 1 41 | end 42 | 43 | RSpec.configure do |config| 44 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 45 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 46 | 47 | config.include RequestSpecHelper, type: :request 48 | config.include ControllerSpecHelper 49 | 50 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 51 | # examples within a transaction, remove the following line or assign false 52 | # instead of true. 53 | config.use_transactional_fixtures = true 54 | 55 | # You can uncomment this line to turn off ActiveRecord support entirely. 56 | # config.use_active_record = false 57 | 58 | # RSpec Rails can automatically mix in different behaviours to your tests 59 | # based on their file location, for example enabling you to call `get` and 60 | # `post` in specs under `spec/controllers`. 61 | # 62 | # You can disable this behaviour by removing the line below, and instead 63 | # explicitly tag your specs with their type, e.g.: 64 | # 65 | # RSpec.describe UsersController, type: :controller do 66 | # # ... 67 | # end 68 | # 69 | # The different available types are documented in the features, such as in 70 | # https://relishapp.com/rspec/rspec-rails/docs 71 | config.infer_spec_type_from_file_location! 72 | 73 | # Filter lines from Rails gems in backtraces. 74 | config.filter_rails_from_backtrace! 75 | # arbitrary gems may also be filtered via: 76 | # config.filter_gems_from_backtrace("gem name") 77 | 78 | # add `FactoryBot` methods 79 | config.include FactoryBot::Syntax::Methods 80 | # start by truncating all the tables but then use the faster transaction strategy the rest of the time. 81 | config.before(:suite) do 82 | DatabaseCleaner.clean_with(:truncation) 83 | DatabaseCleaner.strategy = :transaction 84 | end 85 | # start the transaction strategy as examples are run 86 | config.around(:each) do |example| 87 | DatabaseCleaner.cleaning do 88 | example.run 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.cache_classes = true 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | 18 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 19 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 20 | # config.require_master_key = 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 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 27 | # config.action_controller.asset_host = 'http://assets.example.com' 28 | 29 | # Specifies the header that your server uses for sending files. 30 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 31 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 32 | 33 | # Store uploaded files on the local file system (see config/storage.yml for options). 34 | config.active_storage.service = :local 35 | 36 | # Mount Action Cable outside main process or domain. 37 | # config.action_cable.mount_path = nil 38 | # config.action_cable.url = 'wss://example.com/cable' 39 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 40 | 41 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 42 | # config.force_ssl = true 43 | 44 | # Use the lowest log level to ensure availability of diagnostic information 45 | # when problems arise. 46 | config.log_level = :debug 47 | 48 | # Prepend all log lines with the following tags. 49 | config.log_tags = [:request_id] 50 | 51 | # Use a different cache store in production. 52 | # config.cache_store = :mem_cache_store 53 | 54 | # Use a real queuing backend for Active Job (and separate queues per environment). 55 | # config.active_job.queue_adapter = :resque 56 | # config.active_job.queue_name_prefix = "WebApp_BackEnd_production" 57 | 58 | config.action_mailer.perform_caching = false 59 | 60 | # Ignore bad email addresses and do not raise email delivery errors. 61 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 62 | # config.action_mailer.raise_delivery_errors = false 63 | 64 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 65 | # the I18n.default_locale when a translation cannot be found). 66 | config.i18n.fallbacks = true 67 | 68 | # Send deprecation notices to registered listeners. 69 | config.active_support.deprecation = :notify 70 | 71 | # Use default logging formatter so that PID and timestamp are not suppressed. 72 | config.log_formatter = ::Logger::Formatter.new 73 | 74 | # Use a different logger for distributed setups. 75 | # require 'syslog/logger' 76 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 77 | 78 | if ENV['RAILS_LOG_TO_STDOUT'].present? 79 | logger = ActiveSupport::Logger.new(STDOUT) 80 | logger.formatter = config.log_formatter 81 | config.logger = ActiveSupport::TaggedLogging.new(logger) 82 | end 83 | 84 | # Do not dump schema after migrations. 85 | config.active_record.dump_schema_after_migration = false 86 | 87 | # Inserts middleware to perform automatic connection switching. 88 | # The `database_selector` hash is used to pass options to the DatabaseSelector 89 | # middleware. The `delay` is used to determine how long to wait after a write 90 | # to send a subsequent read to the primary. 91 | # 92 | # The `database_resolver` class is used by the middleware to determine which 93 | # database is appropriate to use based on the time delay. 94 | # 95 | # The `database_resolver_context` class is used by the middleware to set 96 | # timestamps for the last write to the primary. The resolver uses the context 97 | # class timestamps to determine how long to wait before reading from the 98 | # replica. 99 | # 100 | # By default Rails will store a last write timestamp in the session. The 101 | # DatabaseSelector middleware is designed as such you can define your own 102 | # strategy for connection switching and pass that into the middleware through 103 | # these configuration options. 104 | # config.active_record.database_selector = { delay: 2.seconds } 105 | # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver 106 | # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session 107 | end 108 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause 4 | # this file to always be loaded, without a need to explicitly require it in any 5 | # files. 6 | # 7 | # Given that it is always loaded, you are encouraged to keep this file as 8 | # light-weight as possible. Requiring heavyweight dependencies from this file 9 | # will add to the boot time of your test suite on EVERY test run, even for an 10 | # individual file that may not need all of that loaded. Instead, consider making 11 | # a separate helper file that requires the additional dependencies and performs 12 | # the additional setup, and require it from the spec files that actually need 13 | # it. 14 | # 15 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 16 | # require database cleaner at the top level 17 | RSpec.configure do |config| 18 | # rspec-expectations config goes here. You can use an alternate 19 | # assertion/expectation library such as wrong or the stdlib/minitest 20 | # assertions if you prefer. 21 | config.expect_with :rspec do |expectations| 22 | # This option will default to `true` in RSpec 4. It makes the `description` 23 | # and `failure_message` of custom matchers include text for helper methods 24 | # defined using `chain`, e.g.: 25 | # be_bigger_than(2).and_smaller_than(4).description 26 | # # => "be bigger than 2 and smaller than 4" 27 | # ...rather than: 28 | # # => "be bigger than 2" 29 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 30 | end 31 | 32 | # rspec-mocks config goes here. You can use an alternate test double 33 | # library (such as bogus or mocha) by changing the `mock_with` option here. 34 | config.mock_with :rspec do |mocks| 35 | # Prevents you from mocking or stubbing a method that does not exist on 36 | # a real object. This is generally recommended, and will default to 37 | # `true` in RSpec 4. 38 | mocks.verify_partial_doubles = true 39 | end 40 | 41 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 42 | # have no way to turn it off -- the option exists only for backwards 43 | # compatibility in RSpec 3). It causes shared context metadata to be 44 | # inherited by the metadata hash of host groups and examples, rather than 45 | # triggering implicit auto-inclusion in groups with matching metadata. 46 | config.shared_context_metadata_behavior = :apply_to_host_groups 47 | 48 | # The settings below are suggested to provide a good initial experience 49 | # with RSpec, but feel free to customize to your heart's content. 50 | # # This allows you to limit a spec run to individual examples or groups 51 | # # you care about by tagging them with `:focus` metadata. When nothing 52 | # # is tagged with `:focus`, all examples get run. RSpec also provides 53 | # # aliases for `it`, `describe`, and `context` that include `:focus` 54 | # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 55 | # config.filter_run_when_matching :focus 56 | # 57 | # # Allows RSpec to persist some state between runs in order to support 58 | # # the `--only-failures` and `--next-failure` CLI options. We recommend 59 | # # you configure your source control system to ignore this file. 60 | # config.example_status_persistence_file_path = "spec/examples.txt" 61 | # 62 | # # Limits the available syntax to the non-monkey patched syntax that is 63 | # # recommended. For more details, see: 64 | # # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 65 | # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 66 | # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 67 | # config.disable_monkey_patching! 68 | # 69 | # # This setting enables warnings. It's recommended, but in some cases may 70 | # # be too noisy due to issues in dependencies. 71 | # config.warnings = true 72 | # 73 | # # Many RSpec users commonly either run the entire suite or an individual 74 | # # file, and it's useful to allow more verbose output when running an 75 | # # individual spec file. 76 | # if config.files_to_run.one? 77 | # # Use the documentation formatter for detailed output, 78 | # # unless a formatter has already been configured 79 | # # (e.g. via a command-line flag). 80 | # config.default_formatter = "doc" 81 | # end 82 | # 83 | # # Print the 10 slowest examples and example groups at the 84 | # # end of the spec run, to help surface which specs are running 85 | # # particularly slow. 86 | # config.profile_examples = 10 87 | # 88 | # # Run specs in random order to surface order dependencies. If you find an 89 | # # order dependency and want to debug it, you can fix the order by providing 90 | # # the seed, which is printed after each run. 91 | # # --seed 1234 92 | # config.order = :random 93 | # 94 | # # Seed global randomization in this process using the `--seed` CLI option. 95 | # # Setting this allows you to use `--seed` to deterministically reproduce 96 | # # test failures related to randomization by passing the same `--seed` value 97 | # # as the one that triggered the failure. 98 | # Kernel.srand config.seed 99 | end 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-capstone 2 | [![Contributors][contributors-shield]][contributors-url] 3 | [![Forks][forks-shield]][forks-url] 4 | [![Stargazers][stars-shield]][stars-url] 5 | [![Issues][issues-shield]][issues-url] 6 | [![LinkedIn][linkedin-shield2]][linkedin-url2] 7 | [![Hireable][hireable]][hireable-url] 8 | 9 | 10 |12 |
15 |
16 | Explore the docs »
17 |
18 |
19 | View Live Demo
20 | ·
21 | Report Bug
22 | ·
23 | Request Feature
24 | ·
25 | API Repo
26 | ·
27 | WebApp Repo
28 |
48 |
49 |
Install the following to get this project running in your machine:
65 | 66 | ### Instructions 67 | 68 |Follow these steps:
69 | 70 | Clone the Repository 71 | 72 | ```Shell 73 | user@pc:~$ git clone https://github.com/Dandush03/FinalCapstone.BackEnd.git 74 | ``` 75 | 76 | Click on the console and to go to the folder that was created 77 | 78 | ```Shell 79 | user@pc:~$ cd FinalCapstone.BackEnd 80 | ``` 81 | 82 | ```Shell 83 | user@pc:~$ bundle 84 | user@pc:~$ yarn 85 | ``` 86 | 87 | ```Shell 88 | user@pc:~$ rails s 89 | ``` 90 | 91 | Open your browser on [http://localhost:3000](http://localhost:3000) 92 | 93 | ## Testing 94 | 95 | After cloning [this repository](https://github.com/Dandush03/FinalCapstone.BackEnd.git) you can run its test by running the following commands, inside the root folder of the project 96 | 97 | ```Shell 98 | user@pc:~$ rspec 99 | ``` 100 | 101 | It will lunch RSpec right away 102 | 103 | ## Contact 104 | 105 | 👤 **Daniel Laloush** 106 | - Portfolio: [dlaloush.me](https://dlaloush.me) 107 | - LinkedIn: [Daniel Laloush](https://www.linkedin.com/in/daniel-laloush-0a7331a9) 108 | - Github: [@Dandush03](https://github.com/Dandush03) 109 | - Twitter: [@d_laloush](https://twitter.com/d_laloush) 110 | 111 |112 | 113 | Project Link: [https://github.com/Dandush03/FinalCapstone.BackEnd](https://github.com/Dandush03/FinalCapstone.BackEnd) 114 | 115 |
116 | 117 | ## Special Thanks 118 | 119 | Thanks to [Microveser](https://www.microverse.org/) for this great learning expirance and for guiding me on my path as a full stack developer 120 | 121 | Thanks to [Gregoire Vella on Behance](https://www.behance.net/gregoirevella) for his great disgin that serve me as inspiration 122 | 123 | ## 🤝 Contributing 124 | 125 | Contributions, issues and feature requests are welcome! 126 | 127 | Feel free to check the [issues page](https://github.com/Dandush03/FinalCapstone.BackEnd/issues). 128 | 129 | ## Show your support 130 | 131 | Give a ⭐️ if you like this project! 132 | 133 | 134 | [contributors-shield]: https://img.shields.io/github/contributors/Dandush03/React-Calculator.svg?style=flat-square 135 | [contributors-url]: https://github.com/Dandush03/FinalCapstone.BackEnd/graphs/contributors 136 | [forks-shield]: https://img.shields.io/github/forks/Dandush03/FinalCapstone.BackEnd.svg?style=flat-square 137 | [forks-url]: https://github.com/Dandush03/FinalCapstone.BackEnd/network/members 138 | [stars-shield]: https://img.shields.io/github/stars/Dandush03/FinalCapstone.BackEnd.svg?style=flat-square 139 | [stars-url]: https://github.com/Dandush03/FinalCapstone.BackEnd/stargazers 140 | [issues-shield]: https://img.shields.io/github/issues/Dandush03/FinalCapstone.BackEnd.svg?style=flat-square 141 | [issues-url]: https://github.com/Dandush03/FinalCapstone.BackEnd/issues 142 | [license-shield]: https://img.shields.io/github/license/Dandush03/FinalCapstone.BackEnd.svg?style=flat-square 143 | [license-url]: https://github.com/Dandush03/FinalCapstone.BackEnd/blob/master/LICENSE.txt 144 | [linkedin-shield2]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555 145 | [linkedin-url2]: https://www.linkedin.com/in/daniel-laloush/ 146 | [hireable]: https://cdn.rawgit.com/hiendv/hireable/master/styles/flat/yes.svg 147 | [hireable-url]: https://www.linkedin.com/in/daniel-laloush/ 148 | -------------------------------------------------------------------------------- /spec/requests/api/tasks_request_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe 'API Task', type: :request do 4 | # create test user 5 | let!(:user) { create(:user) } 6 | let!(:category) { create(:category) } 7 | let!(:task) { create(:task) } 8 | # set headers for authorization 9 | let(:headers) { valid_headers } 10 | let(:invalid_headers) { valid_headers.except('Authorization') } 11 | # set test valid and invalid credentials 12 | let(:valid_credentials_by_email) do 13 | { 14 | login: user.email, 15 | password: user.password 16 | }.to_json 17 | end 18 | 19 | describe 'API #index' do 20 | context 'GET #index with valid credentials' do 21 | before do 22 | get '/api/tasks', params: {}, headers: headers 23 | end 24 | 25 | it 'returns http success' do 26 | expect(response).to have_http_status(:success) 27 | end 28 | 29 | it 'JSON body response contains a Task List' do 30 | json_response = JSON.parse(response.body) 31 | expect(json_response[0].keys).to match_array( 32 | %w[ 33 | category_id 34 | created_at 35 | description 36 | end 37 | hours 38 | id 39 | minutes 40 | name 41 | seconds 42 | start 43 | updated_at 44 | user_id 45 | ] 46 | ) 47 | end 48 | end 49 | 50 | context 'GET #index with invalid credentials' do 51 | before do 52 | get '/api/tasks', params: {}, headers: invalid_headers 53 | end 54 | 55 | it 'returns http invalid credential Msg' do 56 | expect(json['message']).to match(/Missing token/) 57 | end 58 | 59 | it 'returns http invalid credential Msg' do 60 | expect(response).to have_http_status(422) 61 | end 62 | end 63 | end 64 | 65 | describe 'API #searcher' do 66 | context 'GET #searcher with valid credentials' do 67 | before do 68 | get '/api/searcher', params: {}, headers: headers 69 | end 70 | 71 | it 'returns http success' do 72 | expect(response).to have_http_status(:success) 73 | end 74 | 75 | it 'JSON body response contains a Task List' do 76 | json_response = JSON.parse(response.body) 77 | expect(json_response.keys).to match_array( 78 | %w[ 79 | category_id 80 | created_at 81 | description 82 | end 83 | hours 84 | id 85 | minutes 86 | name 87 | seconds 88 | start 89 | updated_at 90 | user_id 91 | ] 92 | ) 93 | end 94 | end 95 | 96 | context 'GET #searcher with invalid credentials' do 97 | before do 98 | get '/api/searcher', params: {}, headers: invalid_headers 99 | end 100 | 101 | it 'returns http invalid credential Msg' do 102 | expect(json['message']).to match(/Missing token/) 103 | end 104 | 105 | it 'returns http invalid credential Msg' do 106 | expect(response).to have_http_status(422) 107 | end 108 | end 109 | end 110 | 111 | describe 'API #create' do 112 | context 'POST #create with valid credentials' do 113 | let(:task) { build(:task) } 114 | before do 115 | post '/api/tasks', params: { tasks: task }.to_json, headers: headers 116 | end 117 | 118 | it 'returns http success' do 119 | expect(response).to have_http_status(:success) 120 | end 121 | end 122 | 123 | context 'POST #create with invalid credentials' do 124 | before do 125 | post '/api/tasks', params: {}, headers: invalid_headers 126 | end 127 | 128 | it 'returns http invalid credential Msg' do 129 | expect(json['message']).to match(/Missing token/) 130 | end 131 | 132 | it 'returns http invalid credential Msg' do 133 | expect(response).to have_http_status(422) 134 | end 135 | end 136 | end 137 | 138 | describe 'API #update' do 139 | context 'PATCH #update with valid credentials' do 140 | before do 141 | time = Time.now 142 | time = time.to_i * 1000 143 | patch "/api/tasks/#{task.id}", params: { task: { end: time } }.to_json, headers: headers 144 | end 145 | 146 | it 'returns http success' do 147 | expect(response).to have_http_status(:success) 148 | end 149 | end 150 | 151 | context 'PATCH #update with invalid credentials' do 152 | before do 153 | patch "/api/tasks/#{task.id}", params: {}, headers: invalid_headers 154 | end 155 | 156 | it 'returns http invalid credential Msg' do 157 | expect(json['message']).to match(/Missing token/) 158 | end 159 | 160 | it 'returns http invalid credential Msg' do 161 | expect(response).to have_http_status(422) 162 | end 163 | end 164 | end 165 | 166 | describe 'API #seach_by_category' do 167 | let!(:task) { create_list(:task, 3) } 168 | context 'GET #search_by_category with valid credentials' do 169 | before do 170 | get '/api/searcher/by_category_date', params: { range: '1', category_id: category.id }, headers: headers 171 | end 172 | 173 | it 'returns http success' do 174 | expect(response).to have_http_status(:success) 175 | end 176 | 177 | it 'JSON body response contains a Task List' do 178 | json_response = JSON.parse(response.body) 179 | expect(json_response[0].keys).to match_array( 180 | %w[ 181 | category_id 182 | created_at 183 | description 184 | end 185 | hours 186 | id 187 | minutes 188 | name 189 | seconds 190 | start 191 | updated_at 192 | user_id 193 | ] 194 | ) 195 | end 196 | end 197 | 198 | context 'GET #search_by_category with invalid credentials' do 199 | before do 200 | get '/api/searcher/by_category_date', params: {}, headers: invalid_headers 201 | end 202 | 203 | it 'returns http invalid credential Msg' do 204 | expect(json['message']).to match(/Missing token/) 205 | end 206 | 207 | it 'returns http invalid credential Msg' do 208 | expect(response).to have_http_status(422) 209 | end 210 | end 211 | end 212 | end 213 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (6.0.3.2) 5 | actionpack (= 6.0.3.2) 6 | nio4r (~> 2.0) 7 | websocket-driver (>= 0.6.1) 8 | actionmailbox (6.0.3.2) 9 | actionpack (= 6.0.3.2) 10 | activejob (= 6.0.3.2) 11 | activerecord (= 6.0.3.2) 12 | activestorage (= 6.0.3.2) 13 | activesupport (= 6.0.3.2) 14 | mail (>= 2.7.1) 15 | actionmailer (6.0.3.2) 16 | actionpack (= 6.0.3.2) 17 | actionview (= 6.0.3.2) 18 | activejob (= 6.0.3.2) 19 | mail (~> 2.5, >= 2.5.4) 20 | rails-dom-testing (~> 2.0) 21 | actionpack (6.0.3.2) 22 | actionview (= 6.0.3.2) 23 | activesupport (= 6.0.3.2) 24 | rack (~> 2.0, >= 2.0.8) 25 | rack-test (>= 0.6.3) 26 | rails-dom-testing (~> 2.0) 27 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 28 | actiontext (6.0.3.2) 29 | actionpack (= 6.0.3.2) 30 | activerecord (= 6.0.3.2) 31 | activestorage (= 6.0.3.2) 32 | activesupport (= 6.0.3.2) 33 | nokogiri (>= 1.8.5) 34 | actionview (6.0.3.2) 35 | activesupport (= 6.0.3.2) 36 | builder (~> 3.1) 37 | erubi (~> 1.4) 38 | rails-dom-testing (~> 2.0) 39 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 40 | activejob (6.0.3.2) 41 | activesupport (= 6.0.3.2) 42 | globalid (>= 0.3.6) 43 | activemodel (6.0.3.2) 44 | activesupport (= 6.0.3.2) 45 | activerecord (6.0.3.2) 46 | activemodel (= 6.0.3.2) 47 | activesupport (= 6.0.3.2) 48 | activestorage (6.0.3.2) 49 | actionpack (= 6.0.3.2) 50 | activejob (= 6.0.3.2) 51 | activerecord (= 6.0.3.2) 52 | marcel (~> 0.3.1) 53 | activesupport (6.0.3.2) 54 | concurrent-ruby (~> 1.0, >= 1.0.2) 55 | i18n (>= 0.7, < 2) 56 | minitest (~> 5.1) 57 | tzinfo (~> 1.1) 58 | zeitwerk (~> 2.2, >= 2.2.2) 59 | addressable (2.7.0) 60 | public_suffix (>= 2.0.2, < 5.0) 61 | bcrypt (3.1.15) 62 | bindex (0.8.1) 63 | bootsnap (1.4.7) 64 | msgpack (~> 1.0) 65 | builder (3.2.4) 66 | byebug (11.1.3) 67 | capybara (3.33.0) 68 | addressable 69 | mini_mime (>= 0.1.3) 70 | nokogiri (~> 1.8) 71 | rack (>= 1.6.0) 72 | rack-test (>= 0.6.3) 73 | regexp_parser (~> 1.5) 74 | xpath (~> 3.2) 75 | childprocess (3.0.0) 76 | concurrent-ruby (1.1.6) 77 | crass (1.0.6) 78 | database_cleaner (1.8.5) 79 | diff-lcs (1.4.4) 80 | erubi (1.9.0) 81 | factory_bot (6.1.0) 82 | activesupport (>= 5.0.0) 83 | factory_bot_rails (6.1.0) 84 | factory_bot (~> 6.1.0) 85 | railties (>= 5.0.0) 86 | faker (2.13.0) 87 | i18n (>= 1.6, < 2) 88 | ffi (1.13.1) 89 | ffi (1.13.1-x64-mingw32) 90 | foreman (0.87.2) 91 | globalid (0.4.2) 92 | activesupport (>= 4.2.0) 93 | i18n (1.8.5) 94 | concurrent-ruby (~> 1.0) 95 | jwt (2.2.1) 96 | listen (3.2.1) 97 | rb-fsevent (~> 0.10, >= 0.10.3) 98 | rb-inotify (~> 0.9, >= 0.9.10) 99 | loofah (2.6.0) 100 | crass (~> 1.0.2) 101 | nokogiri (>= 1.5.9) 102 | mail (2.7.1) 103 | mini_mime (>= 0.1.1) 104 | marcel (0.3.3) 105 | mimemagic (~> 0.3.2) 106 | method_source (1.0.0) 107 | mimemagic (0.3.5) 108 | mini_mime (1.0.2) 109 | mini_portile2 (2.4.0) 110 | minitest (5.14.1) 111 | msgpack (1.3.3) 112 | msgpack (1.3.3-x64-mingw32) 113 | nio4r (2.5.2) 114 | nokogiri (1.10.10) 115 | mini_portile2 (~> 2.4.0) 116 | nokogiri (1.10.10-x64-mingw32) 117 | mini_portile2 (~> 2.4.0) 118 | pg (1.2.3) 119 | pg (1.2.3-x64-mingw32) 120 | public_suffix (4.0.5) 121 | puma (4.3.5) 122 | nio4r (~> 2.0) 123 | rack (2.2.3) 124 | rack-cors (1.1.1) 125 | rack (>= 2.0.0) 126 | rack-test (1.1.0) 127 | rack (>= 1.0, < 3) 128 | rails (6.0.3.2) 129 | actioncable (= 6.0.3.2) 130 | actionmailbox (= 6.0.3.2) 131 | actionmailer (= 6.0.3.2) 132 | actionpack (= 6.0.3.2) 133 | actiontext (= 6.0.3.2) 134 | actionview (= 6.0.3.2) 135 | activejob (= 6.0.3.2) 136 | activemodel (= 6.0.3.2) 137 | activerecord (= 6.0.3.2) 138 | activestorage (= 6.0.3.2) 139 | activesupport (= 6.0.3.2) 140 | bundler (>= 1.3.0) 141 | railties (= 6.0.3.2) 142 | sprockets-rails (>= 2.0.0) 143 | rails-dom-testing (2.0.3) 144 | activesupport (>= 4.2.0) 145 | nokogiri (>= 1.6) 146 | rails-html-sanitizer (1.3.0) 147 | loofah (~> 2.3) 148 | railties (6.0.3.2) 149 | actionpack (= 6.0.3.2) 150 | activesupport (= 6.0.3.2) 151 | method_source 152 | rake (>= 0.8.7) 153 | thor (>= 0.20.3, < 2.0) 154 | rake (13.0.1) 155 | rb-fsevent (0.10.4) 156 | rb-inotify (0.10.1) 157 | ffi (~> 1.0) 158 | regexp_parser (1.7.1) 159 | rspec-core (3.9.2) 160 | rspec-support (~> 3.9.3) 161 | rspec-expectations (3.9.2) 162 | diff-lcs (>= 1.2.0, < 2.0) 163 | rspec-support (~> 3.9.0) 164 | rspec-mocks (3.9.1) 165 | diff-lcs (>= 1.2.0, < 2.0) 166 | rspec-support (~> 3.9.0) 167 | rspec-rails (4.0.1) 168 | actionpack (>= 4.2) 169 | activesupport (>= 4.2) 170 | railties (>= 4.2) 171 | rspec-core (~> 3.9) 172 | rspec-expectations (~> 3.9) 173 | rspec-mocks (~> 3.9) 174 | rspec-support (~> 3.9) 175 | rspec-support (3.9.3) 176 | rubyzip (2.3.0) 177 | selenium-webdriver (3.142.7) 178 | childprocess (>= 0.5, < 4.0) 179 | rubyzip (>= 1.2.2) 180 | shoulda-matchers (4.3.0) 181 | activesupport (>= 4.2.0) 182 | spring (2.1.0) 183 | spring-watcher-listen (2.0.1) 184 | listen (>= 2.7, < 4.0) 185 | spring (>= 1.2, < 3.0) 186 | sprockets (4.0.2) 187 | concurrent-ruby (~> 1.0) 188 | rack (> 1, < 3) 189 | sprockets-rails (3.2.1) 190 | actionpack (>= 4.0) 191 | activesupport (>= 4.0) 192 | sprockets (>= 3.0.0) 193 | sqlite3 (1.4.2) 194 | thor (1.0.1) 195 | thread_safe (0.3.6) 196 | tzinfo (1.2.7) 197 | thread_safe (~> 0.1) 198 | tzinfo-data (1.2020.1) 199 | tzinfo (>= 1.0.0) 200 | web-console (4.0.4) 201 | actionview (>= 6.0.0) 202 | activemodel (>= 6.0.0) 203 | bindex (>= 0.4.0) 204 | railties (>= 6.0.0) 205 | webdrivers (4.4.1) 206 | nokogiri (~> 1.6) 207 | rubyzip (>= 1.3.0) 208 | selenium-webdriver (>= 3.0, < 4.0) 209 | websocket-driver (0.7.3) 210 | websocket-extensions (>= 0.1.0) 211 | websocket-extensions (0.1.5) 212 | xpath (3.2.0) 213 | nokogiri (~> 1.8) 214 | zeitwerk (2.4.0) 215 | 216 | PLATFORMS 217 | ruby 218 | x64-mingw32 219 | 220 | DEPENDENCIES 221 | bcrypt 222 | bootsnap 223 | byebug 224 | capybara 225 | database_cleaner 226 | factory_bot_rails 227 | faker 228 | foreman 229 | jwt 230 | listen 231 | pg 232 | puma 233 | rack-cors 234 | rails (~> 6.0.3, >= 6.0.3.2) 235 | rspec-rails 236 | shoulda-matchers 237 | spring 238 | spring-watcher-listen 239 | sqlite3 240 | tzinfo-data 241 | web-console 242 | webdrivers 243 | 244 | RUBY VERSION 245 | ruby 2.7.1p83 246 | 247 | BUNDLED WITH 248 | 2.1.4 249 | --------------------------------------------------------------------------------