Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ ruby '3.4.4'

gem 'puma'

gem 'rails', '~> 8.0'
gem 'bootsnap', require: false
gem 'importmap-rails'
gem 'turbo-rails'
gem 'rails', '~> 8.0'
gem 'stimulus-rails'
gem 'turbo-rails'

gem 'solid_queue', '~> 1.1'
gem 'mission_control-jobs'
gem 'solid_queue', '~> 1.1'

gem 'propshaft'

Expand All @@ -29,19 +29,19 @@ gem 'haml-rails'
gem 'ransack'

group :development, :test do
gem 'database_cleaner'
gem 'debug', platforms: %i[mri mingw x64_mingw]
gem 'rspec-rails'
gem 'simplecov', require: false
gem 'database_cleaner'
end

group :development do
gem 'web-console'
gem 'claude-on-rails'
gem 'rubocop', require: false
gem 'rubocop-performance', require: false
gem 'rubocop-rails', require: false
gem 'rubocop-rspec', require: false
gem 'rubocop-performance', require: false
gem 'web-console'
end

group :production do
Expand Down
6 changes: 3 additions & 3 deletions app/channels/application_cable/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ def connect
private

def set_current_user
if session = Session.find_by(id: cookies.signed[:session_id])
self.current_user = session.user
end
return unless session = Session.find_by(id: cookies.signed[:session_id])

self.current_user = session.user
end
end
end
8 changes: 3 additions & 5 deletions app/controllers/categories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,15 @@ def index
end

# GET /categories/1 or /categories/1.json
def show
end
def show; end

# GET /categories/new
def new
@category = Category.new
end

# GET /categories/1/edit
def edit
end
def edit; end

# POST /categories or /categories.json
def create
Expand Down Expand Up @@ -88,7 +86,7 @@ def merge
@old_category.destroy
end
redirect_to merge_categories_path, notice: 'Category was successfully merged.'
rescue ActiveRecord::RecordInvalid => e
rescue ActiveRecord::RecordInvalid
redirect_to merge_categories_path, alert: 'There was an error. Category could not be merged.'
end

Expand Down
18 changes: 8 additions & 10 deletions app/controllers/charts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def show
private

def calculate_monthly_profit_loss(year)
months = (1..12).map { |m| m.to_s }
data = months.map do |month|
months = (1..12).map(&:to_s)
months.map do |month|
entries = current_user.entries
.by_year(year)
.by_month(month)
Expand All @@ -44,40 +44,38 @@ def calculate_monthly_profit_loss(year)
profit_loss: profit_loss
}
end

data
end

def calculate_daily_expenses(year)
start_date = Date.parse("#{year}-01-01")
end_date = Date.parse("#{year}-12-31")

# Get all expense entries for the year
expense_entries = current_user.entries
.by_year(year)
.by_income(false)
.by_untracked(false)
.group(:date)
.select('date, COUNT(*) as count, SUM(amount) as total')

# Convert to hash for easy lookup
expenses_by_date = expense_entries.each_with_object({}) do |entry, hash|
hash[entry.date.to_s] = {
count: entry.count,
total: entry.total.to_f
}
end

# Build complete year data with zeros for days without expenses
daily_data = {}
(start_date..end_date).each do |date|
date_key = date.to_s
daily_data[date_key] = expenses_by_date[date_key] || { count: 0, total: 0.0 }
end

# Calculate max expense for scaling
max_expense = daily_data.values.map { |d| d[:total] }.max || 0
max_expense = daily_data.values.map { |d| d[:total] }.max || 0 # rubocop:disable Rails/Pluck

{
daily_expenses: daily_data,
max_expense: max_expense,
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/concerns/authentication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ module Authentication
end

class_methods do
def allow_unauthenticated_access(**options)
skip_before_action :require_authentication, **options
def allow_unauthenticated_access(**)
skip_before_action(:require_authentication, **)
end
end

Expand Down
5 changes: 2 additions & 3 deletions app/controllers/entries_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ def index
end

# GET /entries/1 or /entries/1.json
def show
end
def show; end

# GET /entries/new
def new
Expand Down Expand Up @@ -84,7 +83,7 @@ def set_entry
# Only allow a list of trusted parameters through.
def entry_params
params.require(:entry).permit(:date, :amount, :notes, :category_name, :income, :untracked, :user_id,
:category_id, tag_attributes: [:id, :name, :_destroy])
:category_id, tag_attributes: %i[id name _destroy])
end

def search_params
Expand Down
24 changes: 11 additions & 13 deletions app/controllers/passwords_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,23 @@ class PasswordsController < ApplicationController
allow_unauthenticated_access
before_action :set_user_by_token, only: %i[edit update]

def new
end
def new; end

def edit; end

def create
if user = User.find_by(email_address: params[:email_address])
# In a real application without email, you might display this to the user or use SMS
# reset_url = edit_password_url(user.password_reset_token)
# Rails.logger.info "Password reset URL for #{user.email_address}: #{reset_url}"
flash[:notice] = 'Please contact an administrator to reset your password. '
else
flash[:notice] = 'Please contact an administrator to reset your password.'
end
flash[:notice] = if User.find_by(email_address: params[:email_address])
# In a real application without email, you might display this to the user or use SMS
# reset_url = edit_password_url(user.password_reset_token)
# Rails.logger.info "Password reset URL for #{user.email_address}: #{reset_url}"
'Please contact an administrator to reset your password. '
else
'Please contact an administrator to reset your password.'
end

redirect_to new_session_path
end

def edit
end

def update
if @user.update(params.permit(:password, :password_confirmation))
redirect_to new_session_path, notice: 'Password has been reset.'
Expand Down
14 changes: 7 additions & 7 deletions app/controllers/pwa_controller.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
class PwaController < ApplicationController
allow_unauthenticated_access

def manifest
render json: render_to_string(template: "pwa/manifest", formats: :json),
content_type: "application/manifest+json"
render json: render_to_string(template: 'pwa/manifest', formats: :json),
content_type: 'application/manifest+json'
end

def service_worker
render file: Rails.public_path.join("service-worker.js"),
content_type: "application/javascript",
render file: Rails.public_path.join('service-worker.js'),
content_type: 'application/javascript',
layout: false
end
end
end
5 changes: 2 additions & 3 deletions app/controllers/recurrables_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ def index
end

# GET /recurrables/1
def show
end
def show; end

# GET /recurrables/new
def new
Expand Down Expand Up @@ -63,6 +62,6 @@ def set_recurrable
# Only allow a list of trusted parameters through.
def recurrable_params
params.require(:recurrable).permit(:name, :day_of_month, :amount, :notes, :category_id,
tag_attributes: [:id, :name, :_destroy])
tag_attributes: %i[id name _destroy])
end
end
5 changes: 2 additions & 3 deletions app/controllers/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
class SessionsController < ApplicationController
allow_unauthenticated_access only: %i[new create]
rate_limit to: 10, within: 3.minutes, only: :create, with: -> {
rate_limit to: 10, within: 3.minutes, only: :create, with: lambda {
redirect_to new_session_url, alert: 'Try again later.'
}

def new
end
def new; end

def create
if user = User.authenticate_by(params.permit(:email_address, :password))
Expand Down
6 changes: 2 additions & 4 deletions app/controllers/tags_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,15 @@ def index
end

# GET /tags/1
def show
end
def show; end

# GET /tags/new
def new
@tag = Tag.new
end

# GET /tags/1/edit
def edit
end
def edit; end

# POST /tags
def create
Expand Down
13 changes: 5 additions & 8 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
class UsersController < ApplicationController
allow_unauthenticated_access only: [:new, :create]
before_action :set_user, only: [:edit, :update]
allow_unauthenticated_access only: %i[new create]
before_action :set_user, only: %i[edit update]

def new
redirect_to root_path if authenticated?
@user = User.new
end

def edit; end

def create
@user = User.new(user_params)

Expand All @@ -18,9 +20,6 @@ def create
end
end

def edit
end

def update
# Allow updating without providing current password
if @user.update(user_update_params)
Expand All @@ -43,9 +42,7 @@ def user_params
def user_update_params
# Only permit password fields if they are present
permitted = [:email_address]
if params[:user][:password].present?
permitted += [:password, :password_confirmation]
end
permitted += %i[password password_confirmation] if params[:user][:password].present?
params.require(:user).permit(permitted)
end
end
2 changes: 1 addition & 1 deletion app/models/category.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class Category < ApplicationRecord
has_and_belongs_to_many :users

def self.ransackable_attributes(auth_object = nil)
def self.ransackable_attributes(_auth_object = nil)
['name']
end
end
4 changes: 2 additions & 2 deletions app/models/concerns/taggable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ def autosave_associated_records_for_tag
self.tag = tag
elsif tag.marked_for_destruction?
self.tag = nil
self.save
save
elsif tag.new_record? && Tag.find_by(name: tag.name).present?
self.tag = Tag.find_by(name: tag.name)
elsif tag.changed? && tag.name.blank?
self.tag = nil
self.save
save
elsif tag.changed?
self.tag = Tag.find_or_create_by(name: tag.name)
end
Expand Down
10 changes: 5 additions & 5 deletions app/models/entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ class Entry < ApplicationRecord
belongs_to :category

scope :by_year, ->(year) { where("strftime('%Y', date) = ?", year.to_s) if year.present? }
scope :by_month, ->(month) {
scope :by_month, lambda { |month|
where("ltrim(strftime('%m', date), '0') = ?", month.to_s) unless month.blank? || month.to_i.zero?
}
scope :by_income, ->(income) { joins(:category).where(categories: { income: income }) if !!income == income }
scope :by_untracked, ->(untracked) {
scope :by_untracked, lambda { |untracked|
joins(:category).where(categories: { untracked: untracked }) if !!untracked == untracked
}
scope :by_category_id, ->(category_id) { where(category_id: category_id) if category_id.present? }
scope :by_tag_id, ->(tag_id) { where(tag_id: tag_id) if tag_id.present? }

def self.ransackable_attributes(auth_object = nil)
['amount', 'date', 'notes']
def self.ransackable_attributes(_auth_object = nil)
%w[amount date notes]
end

def self.ransackable_associations(auth_object = nil)
def self.ransackable_associations(_auth_object = nil)
['category']
end

Expand Down
6 changes: 3 additions & 3 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ def tags
end

def remove_data
self.tags.destroy_all
self.entries.destroy_all
self.categories.destroy_all
tags.destroy_all
entries.destroy_all
categories.destroy_all
end

def password_reset_token
Expand Down
Loading