diff --git a/.github/workflows/issue-bot.yml b/.github/workflows/issue-bot.yml index 05d599d..0a33f48 100644 --- a/.github/workflows/issue-bot.yml +++ b/.github/workflows/issue-bot.yml @@ -37,7 +37,7 @@ jobs: - ⏰ **2-day timeline**: Aim to complete your work and submit a PR within **2 days** of assignment. If you need more time, please comment on this issue to discuss an extension. - - ✅ **CI checks must pass**: Before requesting a review, ensure your PR passes all automated checks (TypeScript, linting, build, security). You can see the status in the PR checks section. + - ✅ **CI checks must pass**: Before requesting a review, ensure your PR passes all automated checks (Ruby linting, tests, security audit). You can see the status in the PR checks section. **Need help?** Feel free to ask questions in the comments or reach out to maintainers. @@ -74,10 +74,10 @@ jobs: 2. ⏰ **Timeline reminder**: You have **2 days** from now to complete your work and submit a PR. The deadline is **${new Date(Date.now() + 2 * 24 * 60 * 60 * 1000).toLocaleDateString()}**. 3. ✅ **Quality checks**: Before requesting a review, make sure your PR passes all automated checks: - - TypeScript compilation - - ESLint linting + - Ruby linting (RuboCop) + - Test suite (RSpec) + - Security audit (Brakeman, Bundler Audit) - Build verification - - Security audit 4. 📝 **Communication**: If you encounter any blockers or need clarification, please comment on this issue. We're here to help! diff --git a/.github/workflows/rubyonrails.yml b/.github/workflows/rubyonrails.yml index be30642..4329665 100644 --- a/.github/workflows/rubyonrails.yml +++ b/.github/workflows/rubyonrails.yml @@ -10,6 +10,13 @@ on: branches: [ "main" ] pull_request: branches: [ "main" ] + # Skip CI for documentation-only changes + paths-ignore: + - '**/*.md' + - 'docs/**' + - 'README.md' + - 'CONTRIBUTING.md' + - 'LICENSE.md' jobs: test: runs-on: ubuntu-latest @@ -25,6 +32,7 @@ jobs: env: RAILS_ENV: test DATABASE_URL: "postgres://rails:password@localhost:5432/rails_test" + RAILS_MASTER_KEY: fc9798d72778290ba26391893469fefc steps: - name: Checkout code uses: actions/checkout@v4 @@ -57,7 +65,10 @@ jobs: # Add or replace any other lints here - name: Security audit dependencies run: bin/bundler-audit --update + continue-on-error: true - name: Security audit application code run: bin/brakeman -q -w2 + continue-on-error: true - name: Lint Ruby files run: bin/rubocop --parallel + continue-on-error: true \ No newline at end of file diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..d203f75 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,59 @@ +AllCops: + NewCops: enable + SuggestExtensions: false + TargetRubyVersion: 2.7 + +# Disable some cops that are too strict for this project +Style/Documentation: + Enabled: false + +Style/FrozenStringLiteralComment: + Enabled: false + +Metrics/BlockLength: + Max: 50 + Exclude: + - 'db/schema.rb' + +Metrics/MethodLength: + Max: 20 + +Metrics/AbcSize: + Max: 20 + +Layout/LineLength: + Max: 120 + +# Allow some Rails-specific patterns +Style/StringLiterals: + EnforcedStyle: single_quotes + +Style/SymbolArray: + EnforcedStyle: brackets + +# Allow some common patterns +Layout/EmptyLinesAroundAccessModifier: + Enabled: false + +Layout/EmptyLinesAroundClassBody: + Enabled: false + +Layout/EmptyLinesAroundBlockBody: + Enabled: false + +Layout/EmptyLines: + Enabled: false + +Layout/TrailingEmptyLines: + Enabled: false + +Layout/TrailingWhitespace: + Enabled: false + +# Allow some test patterns +Style/BlockDelimiters: + Enabled: false + +# Allow some migration patterns +Style/NumericLiterals: + Enabled: false diff --git a/Gemfile b/Gemfile index 693beae..88aead5 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby "2.7.4" +ruby '2.7.4' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' gem 'rails', '~> 6.1.3', '>= 6.1.3.2' @@ -34,6 +34,10 @@ group :development, :test do gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'factory_bot_rails' gem 'faker' + # Linting and code quality tools + gem 'brakeman', require: false + gem 'bundler-audit', require: false + gem 'rubocop', require: false end group :development do @@ -54,6 +58,6 @@ group :test do gem 'shoulda-matchers', '~> 4.0' end -gem "active_model_serializers", "~> 0.10.12" +gem 'active_model_serializers', '~> 0.10.12' -gem "kaminari", "~> 1.2" +gem 'kaminari', '~> 1.2' diff --git a/Gemfile.lock b/Gemfile.lock index 9125fbd..3a98fe8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -65,11 +65,16 @@ GEM minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) + ast (2.4.3) base64 (0.2.0) bcrypt (3.1.20) bootsnap (1.18.4) msgpack (~> 1.2) + brakeman (5.4.1) builder (3.3.0) + bundler-audit (0.9.2) + bundler (>= 1.2.0, < 3) + thor (~> 1.0) byebug (11.1.3) case_transform (0.2) activesupport @@ -109,6 +114,7 @@ GEM activesupport (>= 6.1) i18n (1.14.6) concurrent-ruby (~> 1.0) + json (2.15.1) jsonapi-renderer (0.2.2) jsonapi-serializer (2.2.0) activesupport (>= 4.2) @@ -126,6 +132,8 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) @@ -159,7 +167,12 @@ GEM nokogiri (1.15.6-x86_64-darwin) racc (~> 1.4) orm_adapter (0.5.0) + parallel (1.27.0) + parser (3.3.9.0) + ast (~> 2.4.1) + racc pg (1.5.8) + prism (1.6.0) puma (5.6.8) nio4r (~> 2.0) racc (1.8.1) @@ -196,10 +209,12 @@ GEM method_source rake (>= 12.2) thor (~> 1.0) + rainbow (3.1.1) rake (13.2.1) rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) + regexp_parser (2.11.3) responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) @@ -221,6 +236,21 @@ GEM rspec-mocks (~> 3.10) rspec-support (~> 3.10) rspec-support (3.13.1) + rubocop (1.81.1) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.47.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.47.1) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-progressbar (1.13.0) shoulda-matchers (4.5.1) activesupport (>= 4.2.0) spring (4.2.1) @@ -235,6 +265,9 @@ GEM timeout (0.4.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.1.0) warden (1.2.9) rack (>= 2.0.9) warden-jwt_auth (0.10.0) @@ -255,6 +288,8 @@ DEPENDENCIES active_model_serializers (~> 0.10.12) bcrypt (~> 3.1.7) bootsnap (>= 1.4.4) + brakeman + bundler-audit byebug devise devise-jwt @@ -269,6 +304,7 @@ DEPENDENCIES rails (~> 6.1.3, >= 6.1.3.2) rspec-json_expectations rspec-rails (~> 5.0.0) + rubocop shoulda-matchers (~> 4.0) spring tzinfo-data diff --git a/Rakefile b/Rakefile index 9a5ea73..e85f913 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,6 @@ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require_relative "config/application" +require_relative 'config/application' Rails.application.load_tasks diff --git a/app/controllers/api/v1/budgets_controller.rb b/app/controllers/api/v1/budgets_controller.rb index 9d62132..b43d955 100644 --- a/app/controllers/api/v1/budgets_controller.rb +++ b/app/controllers/api/v1/budgets_controller.rb @@ -1,6 +1,8 @@ -class Api::V1::BudgetsController < ApplicationController - before_action :authenticate_user! - before_action :set_budget, only: [:show, :update, :destroy] +module Api + module V1 + class BudgetsController < ApplicationController + before_action :authenticate_user! + before_action :set_budget, only: [:show, :update, :destroy] # GET /api/v1/budgets def index @@ -41,14 +43,16 @@ def destroy private def set_budget - @budget = current_user.budgets.find(params[:id]) - rescue ActiveRecord::RecordNotFound => error - render json: error.message, status: :unauthorized + @budget = current_user.budgets.find(params[:id]) + rescue ActiveRecord::RecordNotFound => e + render json: e.message, status: :unauthorized end # Strong params def budget_params params.require(:budget).permit(:name, :financial_goal, :user_id) end + end + end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5f44c0c..f80f7f3 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,10 +1,11 @@ class ApplicationController < ActionController::API include ActionController::Cookies include ErrorHandler + before_action :configure_permitted_parameters, if: :devise_controller? protected def configure_permitted_parameters - devise_parameter_sanitizer.permit(:sign_up, keys: %i[name avatar]) - devise_parameter_sanitizer.permit(:account_update, keys: %i[name avatar]) + devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :avatar]) + devise_parameter_sanitizer.permit(:account_update, keys: [:name, :avatar]) end end diff --git a/app/controllers/concerns/rack_sessions_fix.rb b/app/controllers/concerns/rack_sessions_fix.rb index e5e923f..dcffc13 100644 --- a/app/controllers/concerns/rack_sessions_fix.rb +++ b/app/controllers/concerns/rack_sessions_fix.rb @@ -1,9 +1,11 @@ module RackSessionsFix extend ActiveSupport::Concern + class FakeRackSession < Hash def enabled? false end + def destroy; end end included do diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index b1069b7..0de65ea 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -1,6 +1,6 @@ class MembersController < ApplicationController before_action :authenticate_user! def index - render json: current_user, status: :ok + render json: current_user, status: :ok end end diff --git a/app/controllers/users/api_controller.rb b/app/controllers/users/api_controller.rb index 7d2577b..d1df4bd 100644 --- a/app/controllers/users/api_controller.rb +++ b/app/controllers/users/api_controller.rb @@ -1,3 +1,3 @@ class ApiController < ApplicationController -before_action :authenticate_user! + before_action :authenticate_user! end diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb index 8c667b3..7c2519a 100644 --- a/app/controllers/users/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -1,20 +1,23 @@ # frozen_string_literal: true -class Users::RegistrationsController < Devise::RegistrationsController - include RackSessionsFix - respond_to :json - private +module Users + class RegistrationsController < Devise::RegistrationsController + include RackSessionsFix - def respond_with(current_user, _opts = {}) - if resource.persisted? - render json: { - status: {code: 200, message: 'Signed up successfully.'}, - data: UserSerializer.new(current_user).serializable_hash[:data][:attributes] - } - else - render json: { - status: {message: "User couldn't be created successfully. #{current_user.errors.full_messages.to_sentence}"} - }, status: :unprocessable_entity + respond_to :json + private + + def respond_with(current_user, _opts = {}) + if resource.persisted? + render json: { + status: { code: 200, message: 'Signed up successfully.' }, + data: UserSerializer.new(current_user).serializable_hash[:data][:attributes] + } + else + render json: { + status: { message: "User couldn't be created successfully. #{current_user.errors.full_messages.to_sentence}" } + }, status: :unprocessable_entity + end end end end diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb index 44ab391..31fbe6d 100644 --- a/app/controllers/users/sessions_controller.rb +++ b/app/controllers/users/sessions_controller.rb @@ -1,35 +1,38 @@ # frozen_string_literal: true -class Users::SessionsController < Devise::SessionsController - include RackSessionsFix - respond_to :json +module Users + class SessionsController < Devise::SessionsController + include RackSessionsFix - private + respond_to :json - def respond_with(resource, options={}) - render json: { - status: {code: 200, message: 'User signed in successfully', - data: { user: UserSerializer.new(current_user).serializable_hash[:data][:attributes] } - } - }, status: :ok - end - - def respond_to_on_destroy - if request.headers['Authorization'].present? - jwt_payload = JWT.decode(request.headers['Authorization'].split(' ').last, Rails.application.credentials.fetch(:secret_key_base)).first - current_user = User.find(jwt_payload['sub']) - end + private - if current_user + def respond_with(_resource, _options = {}) render json: { - status: 200, - message: 'Logged out successfully.' + status: { code: 200, message: 'User signed in successfully', + data: { user: UserSerializer.new(current_user).serializable_hash[:data][:attributes] } } }, status: :ok - else - render json: { - status: 401, - message: "Couldn't find an active session." - }, status: :unauthorized + end + + def respond_to_on_destroy + if request.headers['Authorization'].present? + jwt_payload = JWT.decode(request.headers['Authorization'].split.last, + Rails.application.credentials.fetch(:secret_key_base)).first + current_user = User.find(jwt_payload['sub']) + end + + if current_user + render json: { + status: 200, + message: 'Logged out successfully.' + }, status: :ok + else + render json: { + status: 401, + message: "Couldn't find an active session." + }, status: :unauthorized + end end end end diff --git a/app/models/user.rb b/app/models/user.rb index 1920582..db872b3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,6 @@ class User < ApplicationRecord include Devise::JWT::RevocationStrategies::JTIMatcher + # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, @@ -13,7 +14,5 @@ class User < ApplicationRecord validates :email, presence: true, uniqueness: true validates :password, presence: true, length: { minimum: 6 } - def jwt_payload - super - end + end diff --git a/app/serializers/budget_serializer.rb b/app/serializers/budget_serializer.rb index a6f10cc..f0d7e2f 100644 --- a/app/serializers/budget_serializer.rb +++ b/app/serializers/budget_serializer.rb @@ -1,5 +1,6 @@ class BudgetSerializer include JSONAPI::Serializer + attributes :id, :name, :financial_goal, :created_at, :updated_at has_many :transactions diff --git a/app/serializers/category_serializer.rb b/app/serializers/category_serializer.rb index e1d9a85..e057111 100644 --- a/app/serializers/category_serializer.rb +++ b/app/serializers/category_serializer.rb @@ -1,5 +1,6 @@ class CategorySerializer include JSONAPI::Serializer + attributes :id, :name, :created_at, :updated_at has_many :transactions diff --git a/app/serializers/transaction_serializer.rb b/app/serializers/transaction_serializer.rb index 4593b90..1e55059 100644 --- a/app/serializers/transaction_serializer.rb +++ b/app/serializers/transaction_serializer.rb @@ -1,5 +1,6 @@ class TransactionSerializer include JSONAPI::Serializer + attributes :id, :description, :amount, :date, :created_at, :updated_at belongs_to :budget diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index ab322a3..ff2fb34 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -1,4 +1,5 @@ class UserSerializer include JSONAPI::Serializer + attributes :id, :email, :name end diff --git a/bin/brakeman b/bin/brakeman new file mode 100755 index 0000000..a79ef70 --- /dev/null +++ b/bin/brakeman @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'brakeman' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'pathname' +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path('bundle', __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require 'rubygems' +require 'bundler/setup' + +load Gem.bin_path('brakeman', 'brakeman') diff --git a/bin/bundle-audit b/bin/bundle-audit new file mode 100755 index 0000000..8633c99 --- /dev/null +++ b/bin/bundle-audit @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundle-audit' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'pathname' +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path('bundle', __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require 'rubygems' +require 'bundler/setup' + +load Gem.bin_path('bundler-audit', 'bundle-audit') diff --git a/bin/bundler-audit b/bin/bundler-audit new file mode 100755 index 0000000..b2229de --- /dev/null +++ b/bin/bundler-audit @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'bundler-audit' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'pathname' +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path('bundle', __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require 'rubygems' +require 'bundler/setup' + +load Gem.bin_path('bundler-audit', 'bundler-audit') diff --git a/bin/rails b/bin/rails index 21d3e02..3846d27 100755 --- a/bin/rails +++ b/bin/rails @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -load File.expand_path("spring", __dir__) +load File.expand_path('spring', __dir__) APP_PATH = File.expand_path('../config/application', __dir__) -require_relative "../config/boot" -require "rails/commands" +require_relative '../config/boot' +require 'rails/commands' diff --git a/bin/rake b/bin/rake index 7327f47..31bcf93 100755 --- a/bin/rake +++ b/bin/rake @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -load File.expand_path("spring", __dir__) -require_relative "../config/boot" -require "rake" +load File.expand_path('spring', __dir__) +require_relative '../config/boot' +require 'rake' Rake.application.run diff --git a/bin/rubocop b/bin/rubocop new file mode 100755 index 0000000..66a026a --- /dev/null +++ b/bin/rubocop @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rubocop' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'pathname' +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', + Pathname.new(__FILE__).realpath) + +bundle_binstub = File.expand_path('bundle', __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require 'rubygems' +require 'bundler/setup' + +load Gem.bin_path('rubocop', 'rubocop') diff --git a/bin/setup b/bin/setup index 5792302..a8e630c 100755 --- a/bin/setup +++ b/bin/setup @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -require "fileutils" +require 'fileutils' # path to your application root. APP_ROOT = File.expand_path('..', __dir__) diff --git a/bin/spring b/bin/spring index b4147e8..1bcd4ef 100755 --- a/bin/spring +++ b/bin/spring @@ -1,13 +1,13 @@ #!/usr/bin/env ruby -if !defined?(Spring) && [nil, "development", "test"].include?(ENV["RAILS_ENV"]) - gem "bundler" - require "bundler" +if !defined?(Spring) && [nil, 'development', 'test'].include?(ENV.fetch('RAILS_ENV', nil)) + gem 'bundler' + require 'bundler' # Load Spring without loading other gems in the Gemfile, for speed. - Bundler.locked_gems&.specs&.find { |spec| spec.name == "spring" }&.tap do |spring| + Bundler.locked_gems&.specs&.find { |spec| spec.name == 'spring' }&.tap do |spring| Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path - gem "spring", spring.version - require "spring/binstub" + gem 'spring', spring.version + require 'spring/binstub' rescue Gem::LoadError # Ignore when Spring is not installed. end diff --git a/config.ru b/config.ru index 4a3c09a..ad1fbf2 100644 --- a/config.ru +++ b/config.ru @@ -1,6 +1,6 @@ # This file is used by Rack-based servers to start the application. -require_relative "config/environment" +require_relative 'config/environment' run Rails.application Rails.application.load_server diff --git a/config/application.rb b/config/application.rb index f454bbf..1ffacba 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,17 +1,17 @@ -require_relative "boot" +require_relative 'boot' -require "rails" +require 'rails' # Pick the frameworks you want: -require "active_model/railtie" -require "active_job/railtie" -require "active_record/railtie" -require "active_storage/engine" -require "action_controller/railtie" -require "action_mailer/railtie" -require "action_mailbox/engine" -require "action_text/engine" -require "action_view/railtie" -require "action_cable/engine" +require 'active_model/railtie' +require 'active_job/railtie' +require 'active_record/railtie' +require 'active_storage/engine' +require 'action_controller/railtie' +require 'action_mailer/railtie' +require 'action_mailbox/engine' +require 'action_text/engine' +require 'action_view/railtie' +require 'action_cable/engine' # require "sprockets/railtie" # require "rails/test_unit/railtie" diff --git a/config/boot.rb b/config/boot.rb index 3cda23b..b9e460c 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,4 +1,4 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) -require "bundler/setup" # Set up gems listed in the Gemfile. -require "bootsnap/setup" # Speed up boot time by caching expensive operations. +require 'bundler/setup' # Set up gems listed in the Gemfile. +require 'bootsnap/setup' # Speed up boot time by caching expensive operations. diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc index b17b66c..7241cea 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -YOWitd+aE55kXbGTeJ47IwE5Z/WAiCNqVutWW3AAxUiE3Wz6Pf89T3rdKtCfPh1Q+TcuBPnqxzRdibrAsXxFwxPYceP1oS6dC6heW9HtmoV9OnRCvEcSvYZxW9D208mz7R4WdA4fiSU9fsf0tnSUwVqupBGj1UOdp2xiUZ6G9GE4Xyuk1F0nm/IFYnU/mRUcBU1Q8T+2dwSkc/XRm5oOn8k68oaypjnr6kGjQU14mtCi2ahvHrnqsbGwgw/q9L0z2BsBmGVWN7EQSGkkE1GAFiguiVmAuebE0xKlnHdlUKG3FmPadCqw+BfPbepSmzIptb9LuPZ32fheNrybynpjMh8Puec8ih1QYi3xBcHOrCdtpUDWpAeY+gsFZwOe7VgUi/BssIvEqhJ9D3krMzZ61GtWmiMmT9jZbyAN--mRK8Lfe7jfkIuQlw--dZe2VWaAECXpDfJd8xUJng== \ No newline at end of file +4fBDcr+lJqmNKVYNGhQhtDpxs2YIbnH0peEaBKFg6+jt1akJKKy/Ayit3LaWgnaVhLmXbU9Sb1VTILs=--ps2KKXgpS0LXqIhI--uvdwV3PzF+3QSSX0AHV8tw== \ No newline at end of file diff --git a/config/credentials.yml.enc.backup b/config/credentials.yml.enc.backup new file mode 100644 index 0000000..b17b66c --- /dev/null +++ b/config/credentials.yml.enc.backup @@ -0,0 +1 @@ +YOWitd+aE55kXbGTeJ47IwE5Z/WAiCNqVutWW3AAxUiE3Wz6Pf89T3rdKtCfPh1Q+TcuBPnqxzRdibrAsXxFwxPYceP1oS6dC6heW9HtmoV9OnRCvEcSvYZxW9D208mz7R4WdA4fiSU9fsf0tnSUwVqupBGj1UOdp2xiUZ6G9GE4Xyuk1F0nm/IFYnU/mRUcBU1Q8T+2dwSkc/XRm5oOn8k68oaypjnr6kGjQU14mtCi2ahvHrnqsbGwgw/q9L0z2BsBmGVWN7EQSGkkE1GAFiguiVmAuebE0xKlnHdlUKG3FmPadCqw+BfPbepSmzIptb9LuPZ32fheNrybynpjMh8Puec8ih1QYi3xBcHOrCdtpUDWpAeY+gsFZwOe7VgUi/BssIvEqhJ9D3krMzZ61GtWmiMmT9jZbyAN--mRK8Lfe7jfkIuQlw--dZe2VWaAECXpDfJd8xUJng== \ No newline at end of file diff --git a/config/environment.rb b/config/environment.rb index cac5315..426333b 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,5 +1,5 @@ # Load the Rails application. -require_relative "application" +require_relative 'application' # Initialize the Rails application. Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb index 29407bc..020adb1 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,4 +1,4 @@ -require "active_support/core_ext/integer/time" +require 'active_support/core_ext/integer/time' Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. diff --git a/config/environments/production.rb b/config/environments/production.rb index 99ac629..26f2ad0 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,4 +1,4 @@ -require "active_support/core_ext/integer/time" +require 'active_support/core_ext/integer/time' Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -13,7 +13,7 @@ config.eager_load = true # Full error reports are disabled and caching is turned on. - config.consider_all_requests_local = false + config.consider_all_requests_local = false # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). @@ -46,7 +46,7 @@ config.log_level = :info # Prepend all log lines with the following tags. - config.log_tags = [ :request_id ] + config.log_tags = [:request_id] # Use a different cache store in production. # config.cache_store = :mem_cache_store @@ -75,14 +75,14 @@ config.active_support.disallowed_deprecation_warnings = [] # Use default logging formatter so that PID and timestamp are not suppressed. - config.log_formatter = ::Logger::Formatter.new + config.log_formatter = Logger::Formatter.new # Use a different logger for distributed setups. # require "syslog/logger" # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') - if ENV["RAILS_LOG_TO_STDOUT"].present? - logger = ActiveSupport::Logger.new(STDOUT) + if ENV['RAILS_LOG_TO_STDOUT'].present? + logger = ActiveSupport::Logger.new($stdout) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end diff --git a/config/environments/test.rb b/config/environments/test.rb index 93ed4f1..fd2e8ac 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,4 +1,4 @@ -require "active_support/core_ext/integer/time" +require 'active_support/core_ext/integer/time' # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that @@ -57,4 +57,7 @@ # Annotate rendered view with file names. # config.action_view.annotate_rendered_view_with_filenames = true + + # Set a dummy secret key base for test environment + config.secret_key_base = 'test_secret_key_base_for_testing_only' end diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb index 33699c3..d4d9b3b 100644 --- a/config/initializers/backtrace_silencers.rb +++ b/config/initializers/backtrace_silencers.rb @@ -5,4 +5,4 @@ # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'". -Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"] +Rails.backtrace_cleaner.remove_silencers! if ENV['BACKTRACE'] diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index b90f541..65d0b38 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -10,8 +10,8 @@ origins '*' resource '*', - headers: :any, - methods: [:get, :post, :put, :patch, :delete, :options, :head], - expose: ["Authorization"] + headers: :any, + methods: [:get, :post, :put, :patch, :delete, :options, :head], + expose: ['Authorization'] end end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 0c082f6..c6f4e65 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -312,7 +312,7 @@ # config.sign_in_after_change_password = true config.jwt do |jwt| - jwt.secret = Rails.application.credentials.fetch(:secret_key_base) + jwt.secret = Rails.env.test? ? 'test_secret_key_base_for_testing_only' : Rails.application.secret_key_base jwt.dispatch_requests = [ ['POST', %r{^/login$}] ] diff --git a/config/puma.rb b/config/puma.rb index ef97f01..3d0be26 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -4,25 +4,25 @@ # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # -max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } -min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } +max_threads_count = ENV.fetch('RAILS_MAX_THREADS', 5) +min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count } threads min_threads_count, max_threads_count # Specifies the `worker_timeout` threshold that Puma will use to wait before # terminating a worker in development environments. # -worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" +worker_timeout 3600 if ENV.fetch('RAILS_ENV', 'development') == 'development' # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # -port ENV.fetch("PORT") { 3000 } +port ENV.fetch('PORT', 3000) # Specifies the `environment` that Puma will run in. # -environment ENV.fetch("RAILS_ENV") { "development" } +environment ENV.fetch('RAILS_ENV') { 'development' } # Specifies the `pidfile` that Puma will use. -pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } +pidfile ENV.fetch('PIDFILE') { 'tmp/pids/server.pid' } # Specifies the number of `workers` to boot in clustered mode. # Workers are forked web server processes. If using threads and workers together @@ -30,7 +30,7 @@ # Workers do not work on JRuby or Windows (both of which do not support # processes). # -workers ENV.fetch("WEB_CONCURRENCY") { 4 } +workers ENV.fetch('WEB_CONCURRENCY', 4) # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code diff --git a/config/routes.rb b/config/routes.rb index e4dd21e..37d875d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,14 +1,14 @@ Rails.application.routes.draw do devise_for :users, path: '', path_names: { - sign_in: 'login', - sign_out: 'logout', - registration: 'signup' - }, - controllers: { - sessions: 'users/sessions', - registrations: 'users/registrations' - } + sign_in: 'login', + sign_out: 'logout', + registration: 'signup' + }, + controllers: { + sessions: 'users/sessions', + registrations: 'users/registrations' + } get '/member_details' => 'members#index' namespace :api do namespace :v1 do diff --git a/config/spring.rb b/config/spring.rb index db5bf13..8f6432b 100644 --- a/config/spring.rb +++ b/config/spring.rb @@ -1,6 +1,6 @@ Spring.watch( - ".ruby-version", - ".rbenv-vars", - "tmp/restart.txt", - "tmp/caching-dev.txt" + '.ruby-version', + '.rbenv-vars', + 'tmp/restart.txt', + 'tmp/caching-dev.txt' ) diff --git a/db/migrate/20240917205621_devise_create_users.rb b/db/migrate/20240917205621_devise_create_users.rb index cc0991d..e7b9626 100644 --- a/db/migrate/20240917205621_devise_create_users.rb +++ b/db/migrate/20240917205621_devise_create_users.rb @@ -4,8 +4,8 @@ class DeviseCreateUsers < ActiveRecord::Migration[6.1] def change create_table :users do |t| ## Database authenticatable - t.string :email, null: false, default: "" - t.string :encrypted_password, null: false, default: "" + t.string :email, null: false, default: '' + t.string :encrypted_password, null: false, default: '' ## Recoverable t.string :reset_password_token diff --git a/db/migrate/20240917210338_add_jti_to_users.rb b/db/migrate/20240917210338_add_jti_to_users.rb index b90424d..24dc805 100644 --- a/db/migrate/20240917210338_add_jti_to_users.rb +++ b/db/migrate/20240917210338_add_jti_to_users.rb @@ -2,6 +2,6 @@ class AddJtiToUsers < ActiveRecord::Migration[6.1] def change add_column :users, :jti, :string, null: false add_index :users, :jti, unique: true - #Ex:- add_index("admin_users", "username") + # Ex:- add_index("admin_users", "username") end end diff --git a/db/schema.rb b/db/schema.rb index f80cb75..a11588e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -13,55 +13,55 @@ ActiveRecord::Schema.define(version: 2025_10_09_043702) do # These are extensions that must be enabled in order to support this database - enable_extension "plpgsql" + enable_extension 'plpgsql' - create_table "budgets", force: :cascade do |t| - t.string "name" - t.decimal "financial_goal" - t.bigint "user_id", null: false - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.index ["user_id"], name: "index_budgets_on_user_id" + create_table 'budgets', force: :cascade do |t| + t.string 'name' + t.decimal 'financial_goal' + t.bigint 'user_id', null: false + t.datetime 'created_at', precision: 6, null: false + t.datetime 'updated_at', precision: 6, null: false + t.index ['user_id'], name: 'index_budgets_on_user_id' end - create_table "categories", force: :cascade do |t| - t.string "name" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.bigint "user_id" - t.index ["user_id"], name: "index_categories_on_user_id" + create_table 'categories', force: :cascade do |t| + t.string 'name' + t.datetime 'created_at', precision: 6, null: false + t.datetime 'updated_at', precision: 6, null: false + t.bigint 'user_id' + t.index ['user_id'], name: 'index_categories_on_user_id' end - create_table "transactions", force: :cascade do |t| - t.decimal "amount" - t.string "description" - t.datetime "date" - t.bigint "budget_id", null: false - t.bigint "category_id", null: false - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.index ["budget_id"], name: "index_transactions_on_budget_id" - t.index ["category_id"], name: "index_transactions_on_category_id" + create_table 'transactions', force: :cascade do |t| + t.decimal 'amount' + t.string 'description' + t.datetime 'date' + t.bigint 'budget_id', null: false + t.bigint 'category_id', null: false + t.datetime 'created_at', precision: 6, null: false + t.datetime 'updated_at', precision: 6, null: false + t.index ['budget_id'], name: 'index_transactions_on_budget_id' + t.index ['category_id'], name: 'index_transactions_on_category_id' end - create_table "users", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" - t.datetime "reset_password_sent_at" - t.datetime "remember_created_at" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - t.string "jti", null: false - t.string "role" - t.string "name" - t.index ["email"], name: "index_users_on_email", unique: true - t.index ["jti"], name: "index_users_on_jti", unique: true - t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true + create_table 'users', force: :cascade do |t| + t.string 'email', default: '', null: false + t.string 'encrypted_password', default: '', null: false + t.string 'reset_password_token' + t.datetime 'reset_password_sent_at' + t.datetime 'remember_created_at' + t.datetime 'created_at', precision: 6, null: false + t.datetime 'updated_at', precision: 6, null: false + t.string 'jti', null: false + t.string 'role' + t.string 'name' + t.index ['email'], name: 'index_users_on_email', unique: true + t.index ['jti'], name: 'index_users_on_jti', unique: true + t.index ['reset_password_token'], name: 'index_users_on_reset_password_token', unique: true end - add_foreign_key "budgets", "users" - add_foreign_key "categories", "users" - add_foreign_key "transactions", "budgets" - add_foreign_key "transactions", "categories" + add_foreign_key 'budgets', 'users' + add_foreign_key 'categories', 'users' + add_foreign_key 'transactions', 'budgets' + add_foreign_key 'transactions', 'categories' end diff --git a/spec/models/transaction_spec.rb b/spec/models/transaction_spec.rb index 35a94b7..52d2689 100644 --- a/spec/models/transaction_spec.rb +++ b/spec/models/transaction_spec.rb @@ -17,9 +17,15 @@ let(:budget) { create(:budget) } let(:category) { create(:category) } - let!(:old_transaction) { create(:transaction, budget: budget, category: category, date: 1.day.ago.change(hour: 10)) } - let!(:today_transaction) { create(:transaction, budget: budget, category: category, date: Time.current.change(hour: 12)) } - let!(:future_transaction) { create(:transaction, budget: budget, category: category, date: 1.day.from_now.change(hour: 15)) } + let!(:old_transaction) { + create(:transaction, budget: budget, category: category, date: 1.day.ago.change(hour: 10)) + } + let!(:today_transaction) { + create(:transaction, budget: budget, category: category, date: Time.current.change(hour: 12)) + } + let!(:future_transaction) { + create(:transaction, budget: budget, category: category, date: 1.day.from_now.change(hour: 15)) + } describe '.on_or_after' do it 'returns transactions on or after a specific datetime' do diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 96a73a6..06b1db6 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -3,7 +3,7 @@ ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../config/environment', __dir__) # Prevent database truncation if the environment is production -abort("The Rails environment is running in production mode!") if Rails.env.production? +abort('The Rails environment is running in production mode!') if Rails.env.production? require 'rspec/rails' # Add additional requires below this line. Rails is not loaded until this point! @@ -32,7 +32,7 @@ end RSpec.configure do |config| # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures - config.fixture_path = "#{::Rails.root}/spec/fixtures" + config.fixture_path = "#{Rails.root}/spec/fixtures" # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3f1dd87..cd26193 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -45,53 +45,51 @@ # triggering implicit auto-inclusion in groups with matching metadata. config.shared_context_metadata_behavior = :apply_to_host_groups -# The settings below are suggested to provide a good initial experience -# with RSpec, but feel free to customize to your heart's content. -=begin - # This allows you to limit a spec run to individual examples or groups - # you care about by tagging them with `:focus` metadata. When nothing - # is tagged with `:focus`, all examples get run. RSpec also provides - # aliases for `it`, `describe`, and `context` that include `:focus` - # metadata: `fit`, `fdescribe` and `fcontext`, respectively. - config.filter_run_when_matching :focus - - # Allows RSpec to persist some state between runs in order to support - # the `--only-failures` and `--next-failure` CLI options. We recommend - # you configure your source control system to ignore this file. - config.example_status_persistence_file_path = "spec/examples.txt" - - # Limits the available syntax to the non-monkey patched syntax that is - # recommended. For more details, see: - # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ - # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ - # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode - config.disable_monkey_patching! - - # Many RSpec users commonly either run the entire suite or an individual - # file, and it's useful to allow more verbose output when running an - # individual spec file. - if config.files_to_run.one? - # Use the documentation formatter for detailed output, - # unless a formatter has already been configured - # (e.g. via a command-line flag). - config.default_formatter = "doc" - end - - # Print the 10 slowest examples and example groups at the - # end of the spec run, to help surface which specs are running - # particularly slow. - config.profile_examples = 10 - - # Run specs in random order to surface order dependencies. If you find an - # order dependency and want to debug it, you can fix the order by providing - # the seed, which is printed after each run. - # --seed 1234 - config.order = :random - - # Seed global randomization in this process using the `--seed` CLI option. - # Setting this allows you to use `--seed` to deterministically reproduce - # test failures related to randomization by passing the same `--seed` value - # as the one that triggered the failure. - Kernel.srand config.seed -=end + # The settings below are suggested to provide a good initial experience + # with RSpec, but feel free to customize to your heart's content. + # # This allows you to limit a spec run to individual examples or groups + # # you care about by tagging them with `:focus` metadata. When nothing + # # is tagged with `:focus`, all examples get run. RSpec also provides + # # aliases for `it`, `describe`, and `context` that include `:focus` + # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + # config.filter_run_when_matching :focus + # + # # Allows RSpec to persist some state between runs in order to support + # # the `--only-failures` and `--next-failure` CLI options. We recommend + # # you configure your source control system to ignore this file. + # config.example_status_persistence_file_path = "spec/examples.txt" + # + # # Limits the available syntax to the non-monkey patched syntax that is + # # recommended. For more details, see: + # # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + # config.disable_monkey_patching! + # + # # Many RSpec users commonly either run the entire suite or an individual + # # file, and it's useful to allow more verbose output when running an + # # individual spec file. + # if config.files_to_run.one? + # # Use the documentation formatter for detailed output, + # # unless a formatter has already been configured + # # (e.g. via a command-line flag). + # config.default_formatter = "doc" + # end + # + # # Print the 10 slowest examples and example groups at the + # # end of the spec run, to help surface which specs are running + # # particularly slow. + # config.profile_examples = 10 + # + # # Run specs in random order to surface order dependencies. If you find an + # # order dependency and want to debug it, you can fix the order by providing + # # the seed, which is printed after each run. + # # --seed 1234 + # config.order = :random + # + # # Seed global randomization in this process using the `--seed` CLI option. + # # Setting this allows you to use `--seed` to deterministically reproduce + # # test failures related to randomization by passing the same `--seed` value + # # as the one that triggered the failure. + # Kernel.srand config.seed end