mirror of
https://github.com/System-End/hackatime.git
synced 2026-04-19 22:15:14 +00:00
* Add goals * Fix up some migrations * Formatting * Simplify migration * Update test/controllers/settings_goals_controller_test.rb Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update test/controllers/settings_goals_controller_test.rb Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Fix svelte-check issues, make CI less janky on dev * svelte-check/fix tests * Fix N+1s * Formatting! * More tests, fix anonymization and N+1 --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
86 lines
2.3 KiB
Ruby
86 lines
2.3 KiB
Ruby
class Goal < ApplicationRecord
|
|
PERIODS = %w[day week month].freeze
|
|
PRESET_TARGET_SECONDS = [
|
|
30.minutes.to_i,
|
|
1.hour.to_i,
|
|
2.hours.to_i,
|
|
4.hours.to_i
|
|
].freeze
|
|
MAX_TARGET_SECONDS_BY_PERIOD = {
|
|
"day" => 24.hours.to_i,
|
|
"week" => 7.days.to_i,
|
|
"month" => 31.days.to_i
|
|
}.freeze
|
|
MAX_GOALS = 5
|
|
|
|
belongs_to :user
|
|
|
|
before_validation :normalize_fields
|
|
|
|
validates :period, inclusion: { in: PERIODS }
|
|
validates :target_seconds, numericality: { only_integer: true, greater_than: 0 }
|
|
validate :languages_must_be_string_array
|
|
validate :projects_must_be_string_array
|
|
validate :target_must_fit_within_period
|
|
validate :max_goals_per_user
|
|
validate :no_duplicate_goal_for_user
|
|
|
|
def as_programming_goal_payload
|
|
{
|
|
id: id.to_s,
|
|
period: period,
|
|
target_seconds: target_seconds,
|
|
languages: languages,
|
|
projects: projects
|
|
}
|
|
end
|
|
|
|
private
|
|
|
|
def normalize_fields
|
|
self.period = period.to_s
|
|
self.languages = Array(languages).map(&:to_s).compact_blank.uniq.sort
|
|
self.projects = Array(projects).map(&:to_s).compact_blank.uniq.sort
|
|
end
|
|
|
|
def languages_must_be_string_array
|
|
return if languages.is_a?(Array) && languages.all? { |language| language.is_a?(String) }
|
|
|
|
errors.add(:languages, "must be an array of strings")
|
|
end
|
|
|
|
def projects_must_be_string_array
|
|
return if projects.is_a?(Array) && projects.all? { |project| project.is_a?(String) }
|
|
|
|
errors.add(:projects, "must be an array of strings")
|
|
end
|
|
|
|
def target_must_fit_within_period
|
|
max_seconds = MAX_TARGET_SECONDS_BY_PERIOD[period]
|
|
return if max_seconds.blank?
|
|
return if target_seconds.to_i <= max_seconds
|
|
|
|
errors.add(:target_seconds, "cannot exceed #{max_seconds / 3600} hours for a #{period} goal")
|
|
end
|
|
|
|
def max_goals_per_user
|
|
return if user.blank?
|
|
|
|
current_goal_count = user.goals.where.not(id: id).count
|
|
return if current_goal_count < MAX_GOALS
|
|
|
|
errors.add(:base, "cannot have more than #{MAX_GOALS} goals")
|
|
end
|
|
|
|
def no_duplicate_goal_for_user
|
|
return if user.blank?
|
|
|
|
duplicate_exists = user.goals
|
|
.where.not(id: id)
|
|
.exists?(period: period, target_seconds: target_seconds, languages: languages, projects: projects)
|
|
|
|
return unless duplicate_exists
|
|
|
|
errors.add(:base, "duplicate goal")
|
|
end
|
|
end
|