2024-04-26 12:59:58 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
2022-04-06 14:46:10 -04:00
|
|
|
class User < ApplicationRecord
|
|
|
|
|
devise :database_authenticatable, :registerable,
|
2024-12-05 11:37:50 -05:00
|
|
|
:recoverable, :rememberable, :validatable, :trackable
|
2024-03-15 18:27:31 -04:00
|
|
|
|
2024-05-25 07:26:56 -04:00
|
|
|
has_many :tracked_points, class_name: 'Point', dependent: :destroy
|
2024-08-25 14:19:02 -04:00
|
|
|
has_many :imports, dependent: :destroy
|
|
|
|
|
has_many :stats, dependent: :destroy
|
|
|
|
|
has_many :exports, dependent: :destroy
|
|
|
|
|
has_many :notifications, dependent: :destroy
|
|
|
|
|
has_many :areas, dependent: :destroy
|
|
|
|
|
has_many :visits, dependent: :destroy
|
|
|
|
|
has_many :points, through: :imports
|
|
|
|
|
has_many :places, through: :visits
|
2025-01-23 10:03:21 -05:00
|
|
|
has_many :trips, dependent: :destroy
|
2024-03-23 16:16:11 -04:00
|
|
|
|
2024-04-04 14:14:11 -04:00
|
|
|
after_create :create_api_key
|
2025-03-12 15:26:53 -04:00
|
|
|
after_create :import_sample_points
|
2025-02-24 18:16:42 -05:00
|
|
|
after_commit :activate, on: :create, if: -> { DawarichSettings.self_hosted? }
|
2025-02-10 14:37:20 -05:00
|
|
|
before_save :sanitize_input
|
2024-04-04 14:14:11 -04:00
|
|
|
|
2024-12-26 15:34:10 -05:00
|
|
|
validates :email, presence: true
|
2025-02-10 14:37:20 -05:00
|
|
|
|
2024-12-26 15:34:10 -05:00
|
|
|
validates :reset_password_token, uniqueness: true, allow_nil: true
|
|
|
|
|
|
|
|
|
|
attribute :admin, :boolean, default: false
|
|
|
|
|
|
2025-02-19 15:23:11 -05:00
|
|
|
enum :status, { inactive: 0, active: 1 }
|
|
|
|
|
|
2025-02-10 14:37:20 -05:00
|
|
|
def safe_settings
|
|
|
|
|
Users::SafeSettings.new(settings)
|
|
|
|
|
end
|
|
|
|
|
|
2024-04-26 12:59:58 -04:00
|
|
|
def countries_visited
|
|
|
|
|
stats.pluck(:toponyms).flatten.map { _1['country'] }.uniq.compact
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def cities_visited
|
|
|
|
|
stats
|
|
|
|
|
.where.not(toponyms: nil)
|
|
|
|
|
.pluck(:toponyms)
|
|
|
|
|
.flatten
|
|
|
|
|
.reject { |toponym| toponym['cities'].blank? }
|
|
|
|
|
.pluck('cities')
|
|
|
|
|
.flatten
|
|
|
|
|
.pluck('city')
|
|
|
|
|
.uniq
|
|
|
|
|
.compact
|
2024-03-23 16:46:18 -04:00
|
|
|
end
|
|
|
|
|
|
2024-08-28 17:54:00 -04:00
|
|
|
def total_distance
|
|
|
|
|
# In km or miles, depending on the application settings (DISTANCE_UNIT)
|
2024-04-26 12:59:58 -04:00
|
|
|
stats.sum(:distance)
|
2024-03-23 16:16:11 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def total_countries
|
2024-04-26 12:59:58 -04:00
|
|
|
countries_visited.size
|
2024-03-23 16:16:11 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def total_cities
|
2024-04-26 12:59:58 -04:00
|
|
|
cities_visited.size
|
2024-03-23 16:16:11 -04:00
|
|
|
end
|
2024-03-24 13:55:35 -04:00
|
|
|
|
2024-12-02 08:44:22 -05:00
|
|
|
def total_reverse_geocoded_points
|
|
|
|
|
tracked_points.where.not(reverse_geocoded_at: nil).count
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def total_reverse_geocoded_points_without_data
|
|
|
|
|
tracked_points.where(geodata: {}).count
|
2024-03-24 13:55:35 -04:00
|
|
|
end
|
2024-04-04 14:14:11 -04:00
|
|
|
|
2024-12-02 11:34:16 -05:00
|
|
|
def immich_integration_configured?
|
|
|
|
|
settings['immich_url'].present? && settings['immich_api_key'].present?
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def photoprism_integration_configured?
|
|
|
|
|
settings['photoprism_url'].present? && settings['photoprism_api_key'].present?
|
|
|
|
|
end
|
|
|
|
|
|
2024-12-06 10:52:36 -05:00
|
|
|
def years_tracked
|
|
|
|
|
Rails.cache.fetch("dawarich/user_#{id}_years_tracked", expires_in: 1.day) do
|
2025-01-07 09:02:35 -05:00
|
|
|
# Use select_all for better performance with large datasets
|
|
|
|
|
sql = <<-SQL
|
|
|
|
|
SELECT DISTINCT
|
|
|
|
|
EXTRACT(YEAR FROM TO_TIMESTAMP(timestamp)) AS year,
|
|
|
|
|
TO_CHAR(TO_TIMESTAMP(timestamp), 'Mon') AS month
|
|
|
|
|
FROM points
|
|
|
|
|
WHERE user_id = #{id}
|
|
|
|
|
ORDER BY year DESC, month ASC
|
|
|
|
|
SQL
|
|
|
|
|
|
|
|
|
|
result = ActiveRecord::Base.connection.select_all(sql)
|
|
|
|
|
|
|
|
|
|
result
|
|
|
|
|
.map { |r| [r['year'].to_i, r['month']] }
|
|
|
|
|
.group_by { |year, _| year }
|
|
|
|
|
.transform_values { |year_data| year_data.map { |_, month| month } }
|
2024-12-11 14:34:49 -05:00
|
|
|
.map { |year, months| { year: year, months: months } }
|
2024-12-06 10:52:36 -05:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2024-04-04 14:14:11 -04:00
|
|
|
private
|
|
|
|
|
|
|
|
|
|
def create_api_key
|
|
|
|
|
self.api_key = SecureRandom.hex(16)
|
2024-05-23 14:12:23 -04:00
|
|
|
|
2024-04-04 14:14:11 -04:00
|
|
|
save
|
|
|
|
|
end
|
2024-12-02 10:52:05 -05:00
|
|
|
|
2025-02-19 15:23:11 -05:00
|
|
|
def activate
|
2025-02-24 18:16:42 -05:00
|
|
|
update(status: :active)
|
2025-02-19 15:23:11 -05:00
|
|
|
end
|
|
|
|
|
|
2025-02-10 14:37:20 -05:00
|
|
|
def sanitize_input
|
2024-12-02 11:22:36 -05:00
|
|
|
settings['immich_url']&.gsub!(%r{/+\z}, '')
|
|
|
|
|
settings['photoprism_url']&.gsub!(%r{/+\z}, '')
|
2025-02-10 14:48:16 -05:00
|
|
|
settings.try(:[], 'maps')&.try(:[], 'url')&.strip!
|
2024-12-02 10:52:05 -05:00
|
|
|
end
|
2025-03-12 15:26:53 -04:00
|
|
|
|
2025-03-24 15:58:43 -04:00
|
|
|
# rubocop:disable Metrics/MethodLength
|
2025-03-12 15:26:53 -04:00
|
|
|
def import_sample_points
|
|
|
|
|
return unless Rails.env.development? ||
|
|
|
|
|
Rails.env.production? ||
|
|
|
|
|
(Rails.env.test? && ENV['IMPORT_SAMPLE_POINTS'])
|
|
|
|
|
|
|
|
|
|
import = imports.create(
|
|
|
|
|
name: 'DELETE_ME_this_is_a_demo_import_DELETE_ME',
|
2025-03-23 13:37:10 -04:00
|
|
|
source: 'gpx'
|
2025-03-12 15:26:53 -04:00
|
|
|
)
|
|
|
|
|
|
2025-03-23 13:37:10 -04:00
|
|
|
import.file.attach(
|
|
|
|
|
Rack::Test::UploadedFile.new(
|
|
|
|
|
Rails.root.join('lib/assets/sample_points.gpx'), 'application/xml'
|
|
|
|
|
)
|
|
|
|
|
)
|
2025-03-12 15:26:53 -04:00
|
|
|
end
|
2025-03-24 15:58:43 -04:00
|
|
|
# rubocop:enable Metrics/MethodLength
|
2022-04-06 14:46:10 -04:00
|
|
|
end
|