diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js index def56c3a..b9eb489e 100644 --- a/app/javascript/controllers/maps_controller.js +++ b/app/javascript/controllers/maps_controller.js @@ -1913,6 +1913,7 @@ export default class extends BaseController { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.apiKey}` }, body: JSON.stringify({ api_key: this.apiKey diff --git a/app/models/point.rb b/app/models/point.rb index 0ca0ac11..e097a82c 100644 --- a/app/models/point.rb +++ b/app/models/point.rb @@ -33,7 +33,7 @@ class Point < ApplicationRecord after_create :async_reverse_geocode, if: -> { DawarichSettings.store_geodata? && !reverse_geocoded? } after_create :set_country after_create_commit :broadcast_coordinates - after_create_commit :trigger_incremental_track_generation, if: -> { import_id.nil? } # Only for real-time points + after_create_commit :trigger_incremental_track_generation, if: -> { import_id.nil? } after_commit :recalculate_track, on: :update def self.without_raw_data @@ -66,6 +66,20 @@ class Point < ApplicationRecord Country.containing_point(lon, lat) end + def self.normalize_timestamp(timestamp) + case timestamp + when Integer + timestamp + when String, Numeric, DateTime, Time + timestamp.to_i + when nil + raise ArgumentError, 'Timestamp cannot be nil' + else + raise ArgumentError, "Cannot convert timestamp to integer: #{timestamp.class}" + end + end + + private # rubocop:disable Metrics/MethodLength Metrics/AbcSize diff --git a/app/services/gpx/track_importer.rb b/app/services/gpx/track_importer.rb index 0bb0d516..18ed0846 100644 --- a/app/services/gpx/track_importer.rb +++ b/app/services/gpx/track_importer.rb @@ -42,7 +42,7 @@ class Gpx::TrackImporter { lonlat: "POINT(#{point['lon'].to_d} #{point['lat'].to_d})", altitude: point['ele'].to_i, - timestamp: Time.parse(point['time']).to_i, + timestamp: Point.normalize_timestamp(point['time']), import_id: import.id, velocity: speed(point), raw_data: point, diff --git a/app/services/immich/import_geodata.rb b/app/services/immich/import_geodata.rb index 9f9679ee..658e44c5 100644 --- a/app/services/immich/import_geodata.rb +++ b/app/services/immich/import_geodata.rb @@ -56,7 +56,7 @@ class Immich::ImportGeodata latitude: asset['exifInfo']['latitude'], longitude: asset['exifInfo']['longitude'], lonlat: "SRID=4326;POINT(#{asset['exifInfo']['longitude']} #{asset['exifInfo']['latitude']})", - timestamp: Time.zone.parse(asset['exifInfo']['dateTimeOriginal']).to_i + timestamp: Point.normalize_timestamp(asset['exifInfo']['dateTimeOriginal']) } end diff --git a/app/services/overland/params.rb b/app/services/overland/params.rb index 40c33599..e8c49fca 100644 --- a/app/services/overland/params.rb +++ b/app/services/overland/params.rb @@ -16,7 +16,7 @@ class Overland::Params lonlat: "POINT(#{point[:geometry][:coordinates][0]} #{point[:geometry][:coordinates][1]})", battery_status: point[:properties][:battery_state], battery: battery_level(point[:properties][:battery_level]), - timestamp: DateTime.parse(point[:properties][:timestamp]), + timestamp: Point.normalize_timestamp(point[:properties][:timestamp]), altitude: point[:properties][:altitude], velocity: point[:properties][:speed], tracker_id: point[:properties][:device_id], diff --git a/app/services/photoprism/import_geodata.rb b/app/services/photoprism/import_geodata.rb index c31946c1..464400da 100644 --- a/app/services/photoprism/import_geodata.rb +++ b/app/services/photoprism/import_geodata.rb @@ -66,7 +66,7 @@ class Photoprism::ImportGeodata latitude: asset['Lat'], longitude: asset['Lng'], lonlat: "SRID=4326;POINT(#{asset['Lng']} #{asset['Lat']})", - timestamp: Time.zone.parse(asset['TakenAt']).to_i + timestamp: Point.normalize_timestamp(asset['TakenAt']) } end diff --git a/app/services/points/params.rb b/app/services/points/params.rb index 521c8040..ea2f0c03 100644 --- a/app/services/points/params.rb +++ b/app/services/points/params.rb @@ -17,7 +17,7 @@ class Points::Params lonlat: lonlat(point), battery_status: point[:properties][:battery_state], battery: battery_level(point[:properties][:battery_level]), - timestamp: DateTime.parse(point[:properties][:timestamp]), + timestamp: normalize_timestamp(point[:properties][:timestamp]), altitude: point[:properties][:altitude], tracker_id: point[:properties][:device_id], velocity: point[:properties][:speed], @@ -48,4 +48,8 @@ class Points::Params def lonlat(point) "POINT(#{point[:geometry][:coordinates][0]} #{point[:geometry][:coordinates][1]})" end + + def normalize_timestamp(timestamp) + Point.normalize_timestamp(DateTime.parse(timestamp)) + end end diff --git a/app/services/tracks/generator.rb b/app/services/tracks/generator.rb index 4da74114..dafb3f83 100644 --- a/app/services/tracks/generator.rb +++ b/app/services/tracks/generator.rb @@ -51,7 +51,7 @@ module Tracks Point.transaction do # Clean up existing tracks if needed - track_cleaner.cleanup_if_needed + track_cleaner.cleanup # Load points using the configured strategy points = point_loader.load_points diff --git a/app/services/tracks/redis_buffer.rb b/app/services/tracks/redis_buffer.rb index 55bc4d82..2262c7a4 100644 --- a/app/services/tracks/redis_buffer.rb +++ b/app/services/tracks/redis_buffer.rb @@ -11,8 +11,6 @@ class Tracks::RedisBuffer @day = day.is_a?(Date) ? day : Date.parse(day.to_s) end - # Store buffered points for an incomplete track segment - # @param points [Array] array of Point objects to buffer def store(points) return if points.empty? @@ -23,8 +21,6 @@ class Tracks::RedisBuffer Rails.logger.debug "Stored #{points.size} points in buffer for user #{user_id}, day #{day}" end - # Retrieve buffered points for the user/day combination - # @return [Array] array of point hashes or empty array if no buffer exists def retrieve redis_key = buffer_key cached_data = Rails.cache.read(redis_key) @@ -44,8 +40,6 @@ class Tracks::RedisBuffer Rails.logger.debug "Cleared buffer for user #{user_id}, day #{day}" end - # Check if a buffer exists for the user/day combination - # @return [Boolean] true if buffer exists, false otherwise def exists? Rails.cache.exist?(buffer_key) end diff --git a/app/services/tracks/segmentation.rb b/app/services/tracks/segmentation.rb index 7043e8c3..e52cc3d8 100644 --- a/app/services/tracks/segmentation.rb +++ b/app/services/tracks/segmentation.rb @@ -42,9 +42,6 @@ module Tracks::Segmentation private - # Split an array of points into track segments based on time and distance thresholds - # @param points [Array] array of Point objects or point hashes - # @return [Array] array of point segments def split_points_into_segments(points) return [] if points.empty? @@ -67,10 +64,6 @@ module Tracks::Segmentation segments end - # Check if a new segment should start based on time and distance thresholds - # @param current_point [Point, Hash] current point (Point object or hash) - # @param previous_point [Point, Hash, nil] previous point or nil - # @return [Boolean] true if new segment should start def should_start_new_segment?(current_point, previous_point) return false if previous_point.nil? @@ -91,10 +84,6 @@ module Tracks::Segmentation false end - # Calculate distance between two points in kilometers - # @param point1 [Point, Hash] first point - # @param point2 [Point, Hash] second point - # @return [Float] distance in kilometers def calculate_distance_kilometers_between_points(point1, point2) lat1, lon1 = point_coordinates(point1) lat2, lon2 = point_coordinates(point2) @@ -103,10 +92,6 @@ module Tracks::Segmentation Geocoder::Calculations.distance_between([lat1, lon1], [lat2, lon2], units: :km) end - # Check if a segment should be finalized (has a large enough gap at the end) - # @param segment_points [Array] array of points in the segment - # @param grace_period_minutes [Integer] grace period in minutes (default 5) - # @return [Boolean] true if segment should be finalized def should_finalize_segment?(segment_points, grace_period_minutes = 5) return false if segment_points.size < 2 @@ -121,22 +106,19 @@ module Tracks::Segmentation time_since_last_point > grace_period_seconds end - # Extract timestamp from point (handles both Point objects and hashes) - # @param point [Point, Hash] point object or hash - # @return [Integer] timestamp as integer def point_timestamp(point) if point.respond_to?(:timestamp) + # Point objects from database always have integer timestamps point.timestamp elsif point.is_a?(Hash) - point[:timestamp] || point['timestamp'] + # Hash might come from Redis buffer or test data + timestamp = point[:timestamp] || point['timestamp'] + timestamp.to_i else raise ArgumentError, "Invalid point type: #{point.class}" end end - # Extract coordinates from point (handles both Point objects and hashes) - # @param point [Point, Hash] point object or hash - # @return [Array] [lat, lon] coordinates def point_coordinates(point) if point.respond_to?(:lat) && point.respond_to?(:lon) [point.lat, point.lon] diff --git a/app/services/tracks/track_cleaners/no_op_cleaner.rb b/app/services/tracks/track_cleaners/no_op_cleaner.rb index 8de3a565..c5f76087 100644 --- a/app/services/tracks/track_cleaners/no_op_cleaner.rb +++ b/app/services/tracks/track_cleaners/no_op_cleaner.rb @@ -7,7 +7,7 @@ module Tracks @user = user end - def cleanup_if_needed + def cleanup # No cleanup needed for incremental processing # We only append new tracks, don't remove existing ones end diff --git a/app/services/tracks/track_cleaners/replace_cleaner.rb b/app/services/tracks/track_cleaners/replace_cleaner.rb index ff295179..e586b49d 100644 --- a/app/services/tracks/track_cleaners/replace_cleaner.rb +++ b/app/services/tracks/track_cleaners/replace_cleaner.rb @@ -24,7 +24,7 @@ # # Example usage: # cleaner = Tracks::TrackCleaners::ReplaceCleaner.new(user, start_at: 1.week.ago, end_at: Time.current) -# cleaner.cleanup_if_needed +# cleaner.cleanup # module Tracks module TrackCleaners @@ -37,7 +37,7 @@ module Tracks @end_at = end_at end - def cleanup_if_needed + def cleanup tracks_to_remove = find_tracks_to_remove if tracks_to_remove.any? diff --git a/spec/services/tracks/generator_spec.rb b/spec/services/tracks/generator_spec.rb index 3d780a4d..2463c1bf 100644 --- a/spec/services/tracks/generator_spec.rb +++ b/spec/services/tracks/generator_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Tracks::Generator do describe '#call' do context 'with no points to process' do before do - allow(track_cleaner).to receive(:cleanup_if_needed) + allow(track_cleaner).to receive(:cleanup) allow(point_loader).to receive(:load_points).and_return([]) end @@ -54,7 +54,7 @@ RSpec.describe Tracks::Generator do end before do - allow(track_cleaner).to receive(:cleanup_if_needed) + allow(track_cleaner).to receive(:cleanup) allow(point_loader).to receive(:load_points).and_return(points) allow(incomplete_segment_handler).to receive(:should_finalize_segment?).and_return(true) allow(incomplete_segment_handler).to receive(:cleanup_processed_data) @@ -91,7 +91,7 @@ RSpec.describe Tracks::Generator do end before do - allow(track_cleaner).to receive(:cleanup_if_needed) + allow(track_cleaner).to receive(:cleanup) allow(point_loader).to receive(:load_points).and_return(points) allow(incomplete_segment_handler).to receive(:should_finalize_segment?).and_return(false) allow(incomplete_segment_handler).to receive(:handle_incomplete_segment) @@ -129,7 +129,7 @@ RSpec.describe Tracks::Generator do end before do - allow(track_cleaner).to receive(:cleanup_if_needed) + allow(track_cleaner).to receive(:cleanup) allow(point_loader).to receive(:load_points).and_return(old_points + recent_points) # First segment (old points) should be finalized @@ -168,7 +168,7 @@ RSpec.describe Tracks::Generator do end before do - allow(track_cleaner).to receive(:cleanup_if_needed) + allow(track_cleaner).to receive(:cleanup) allow(point_loader).to receive(:load_points).and_return(single_point) allow(incomplete_segment_handler).to receive(:should_finalize_segment?).and_return(true) allow(incomplete_segment_handler).to receive(:cleanup_processed_data) @@ -186,7 +186,7 @@ RSpec.describe Tracks::Generator do context 'error handling' do before do - allow(track_cleaner).to receive(:cleanup_if_needed) + allow(track_cleaner).to receive(:cleanup) allow(point_loader).to receive(:load_points).and_raise(StandardError, 'Point loading failed') end