diff --git a/.app_version b/.app_version index c24a3959..a5510516 100644 --- a/.app_version +++ b/.app_version @@ -1 +1 @@ -0.14.7 +0.15.0 diff --git a/.gitignore b/.gitignore index 909855de..881769d1 100644 --- a/.gitignore +++ b/.gitignore @@ -24,12 +24,22 @@ /tmp/storage/* !/tmp/storage/ !/tmp/storage/.keep +/tmp/imports/* +!/tmp/imports/ +/tmp/imports/watched/* +!/tmp/imports/watched/ +!/tmp/imports/watched/.keep +!/tmp/imports/watched/put-your-files-here.txt + /public/assets -# We need directories for import and export files, but not the files themselves. +# Ignore all files under /public/exports except the .keep file /public/exports/* !/public/exports/.keep +!/public/exports/ + +# Ignore all files under /public/imports, but keep .keep files and the watched directory /public/imports/* !/public/imports/.keep diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eb9e7c9..c5aaa92c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +# 0.15.0 - 2024-10-03 + +### Added + +- You can now put your GPX and GeoJSON files to `tmp/imports/watched` directory and Dawarich will automatically import them. This is useful if you have a service that can put files to the directory automatically. The directory is being watched every 60 minutes for new files. + +### Changed + +- Monkey patch for Geocoder to support http along with https for Photon API host was removed becausee it was breaking the reverse geocoding process. Now you can use only https for the Photon API host. This might be changed in the future +- Disable retries for some background jobs + +### Fixed + +- Stats update is now being correctly triggered every 6 hours + # [0.14.7] - 2024-10-01 ### Fixed diff --git a/app/jobs/area_visits_calculating_job.rb b/app/jobs/area_visits_calculating_job.rb index fe74ff9d..95850286 100644 --- a/app/jobs/area_visits_calculating_job.rb +++ b/app/jobs/area_visits_calculating_job.rb @@ -2,6 +2,7 @@ class AreaVisitsCalculatingJob < ApplicationJob queue_as :default + sidekiq_options retry: false def perform(user_id) user = User.find(user_id) diff --git a/app/jobs/area_visits_calculation_scheduling_job.rb b/app/jobs/area_visits_calculation_scheduling_job.rb index a1addc82..db4c5d3e 100644 --- a/app/jobs/area_visits_calculation_scheduling_job.rb +++ b/app/jobs/area_visits_calculation_scheduling_job.rb @@ -2,6 +2,7 @@ class AreaVisitsCalculationSchedulingJob < ApplicationJob queue_as :default + sidekiq_options retry: false def perform User.find_each { AreaVisitsCalculatingJob.perform_later(_1.id) } diff --git a/app/jobs/enqueue_background_job.rb b/app/jobs/enqueue_background_job.rb index 6dc26b58..aa5cdccf 100644 --- a/app/jobs/enqueue_background_job.rb +++ b/app/jobs/enqueue_background_job.rb @@ -6,7 +6,7 @@ class EnqueueBackgroundJob < ApplicationJob def perform(job_name, user_id) case job_name when 'start_immich_import' - ImportImmichGeodataJob.perform_later(user_id) + Import::ImmichGeodataJob.perform_later(user_id) when 'start_reverse_geocoding', 'continue_reverse_geocoding' Jobs::Create.new(job_name, user_id).call else diff --git a/app/jobs/import_google_takeout_job.rb b/app/jobs/import/google_takeout_job.rb similarity index 84% rename from app/jobs/import_google_takeout_job.rb rename to app/jobs/import/google_takeout_job.rb index 6a3faf60..d962a304 100644 --- a/app/jobs/import_google_takeout_job.rb +++ b/app/jobs/import/google_takeout_job.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ImportGoogleTakeoutJob < ApplicationJob +class Import::GoogleTakeoutJob < ApplicationJob queue_as :imports sidekiq_options retry: false diff --git a/app/jobs/import_immich_geodata_job.rb b/app/jobs/import/immich_geodata_job.rb similarity index 66% rename from app/jobs/import_immich_geodata_job.rb rename to app/jobs/import/immich_geodata_job.rb index d4b63b45..01d9eb9d 100644 --- a/app/jobs/import_immich_geodata_job.rb +++ b/app/jobs/import/immich_geodata_job.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true -class ImportImmichGeodataJob < ApplicationJob +class Import::ImmichGeodataJob < ApplicationJob queue_as :imports + sidekiq_options retry: false def perform(user_id) user = User.find(user_id) diff --git a/app/jobs/import/watcher_job.rb b/app/jobs/import/watcher_job.rb new file mode 100644 index 00000000..57ae24bd --- /dev/null +++ b/app/jobs/import/watcher_job.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class Import::WatcherJob < ApplicationJob + queue_as :imports + sidekiq_options retry: false + + def perform + Imports::Watcher.new.call + end +end diff --git a/app/jobs/stat_creating_job.rb b/app/jobs/stat_creating_job.rb index 26d84b99..d4bd97de 100644 --- a/app/jobs/stat_creating_job.rb +++ b/app/jobs/stat_creating_job.rb @@ -4,6 +4,8 @@ class StatCreatingJob < ApplicationJob queue_as :stats def perform(user_ids = nil) + user_ids = user_ids.nil? ? User.pluck(:id) : Array(user_ids) + CreateStats.new(user_ids).call end end diff --git a/app/jobs/visit_suggesting_job.rb b/app/jobs/visit_suggesting_job.rb index a5090e82..06883b64 100644 --- a/app/jobs/visit_suggesting_job.rb +++ b/app/jobs/visit_suggesting_job.rb @@ -2,6 +2,7 @@ class VisitSuggestingJob < ApplicationJob queue_as :visit_suggesting + sidekiq_options retry: false def perform(user_ids: [], start_at: 1.day.ago, end_at: Time.current) users = user_ids.any? ? User.where(id: user_ids) : User.all diff --git a/app/services/imports/watcher.rb b/app/services/imports/watcher.rb new file mode 100644 index 00000000..8c153bb1 --- /dev/null +++ b/app/services/imports/watcher.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +class Imports::Watcher + class UnsupportedSourceError < StandardError; end + + WATCHED_DIR_PATH = Rails.root.join('tmp/imports/watched') + + def call + %w[*.gpx *.json].each do |pattern| + Dir[WATCHED_DIR_PATH.join(pattern)].each do |file_path| + # valid file_name example: "email@dawarich.app_2024-01-01-2024-01-31.json" + file_name = File.basename(file_path) + + user = find_user(file_name) + next unless user + + import = find_or_initialize_import(user, file_name) + + next if import.persisted? + + import_id = set_import_attributes(import, file_path, file_name) + + ImportJob.perform_later(user.id, import_id) + end + end + end + + private + + def find_user(file_name) + email = file_name.split('_').first + + User.find_by(email:) + end + + def find_or_initialize_import(user, file_name) + import_name = file_name.split('_')[1..].join('_') + + Import.find_or_initialize_by(user:, name: import_name) + end + + def set_import_attributes(import, file_path, file_name) + source = source(file_name) + + import.source = source + import.raw_data = raw_data(file_path, source) + + import.save! + + import.id + end + + def source(file_name) + case file_name.split('.').last + when 'json' then :geojson + when 'gpx' then :gpx + else raise UnsupportedSourceError, 'Unsupported source ' + end + end + + def raw_data(file_path, source) + file = File.read(file_path) + + source == :gpx ? Hash.from_xml(file) : JSON.parse(file) + end +end diff --git a/app/services/tasks/imports/google_records.rb b/app/services/tasks/imports/google_records.rb index f130180b..8f8839e3 100644 --- a/app/services/tasks/imports/google_records.rb +++ b/app/services/tasks/imports/google_records.rb @@ -32,7 +32,7 @@ class Tasks::Imports::GoogleRecords def schedule_import_jobs(json_data, import_id) json_data['locations'].each do |json| - ImportGoogleTakeoutJob.perform_later(import_id, json.to_json) + Import::GoogleTakeoutJob.perform_later(import_id, json.to_json) end end diff --git a/config/initializers/00_monkey_patching.rb b/config/initializers/00_monkey_patching.rb deleted file mode 100644 index 89c73c2d..00000000 --- a/config/initializers/00_monkey_patching.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -# By default, Geocoder supports only https protocol when talking to Photon API. -# This is kinda inconvenient when you're running a local instance of Photon -# and want to use http protocol. This monkey patch allows you to do that. - -module Geocoder::Lookup - class Photon < Base - private - - def supported_protocols - %i[https http] - end - end -end diff --git a/config/schedule.yml b/config/schedule.yml index 487c7bf9..fd6b45b9 100644 --- a/config/schedule.yml +++ b/config/schedule.yml @@ -1,16 +1,21 @@ # config/schedule.yml stat_creating_job: - cron: "0 */6 * * *" # every 6 hours + cron: "0 */6 * * *" # every 6 hour class: "StatCreatingJob" - queue: default + queue: stats area_visits_calculation_scheduling_job: cron: "0 0 * * *" # every day at 0:00 class: "AreaVisitsCalculationSchedulingJob" - queue: default + queue: visit_suggesting visit_suggesting_job: cron: "0 1 * * *" # every day at 1:00 class: "VisitSuggestingJob" - queue: default + queue: visit_suggesting + +watcher_job: + cron: "0 */1 * * *" # every 1 hour + class: "Import::WatcherJob" + queue: imports diff --git a/spec/fixtures/files/watched/user@domain.com_export_same_points.json b/spec/fixtures/files/watched/user@domain.com_export_same_points.json new file mode 100644 index 00000000..2ecfb883 --- /dev/null +++ b/spec/fixtures/files/watched/user@domain.com_export_same_points.json @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":["37.6173","55.755826"]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459200,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{}}},{"type":"Feature","geometry":{"type":"Point","coordinates":["37.6173","55.755826"]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459200,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{}}},{"type":"Feature","geometry":{"type":"Point","coordinates":["37.6173","55.755826"]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459200,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{}}},{"type":"Feature","geometry":{"type":"Point","coordinates":["37.6173","55.755826"]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459200,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{}}},{"type":"Feature","geometry":{"type":"Point","coordinates":["37.6173","55.755826"]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459200,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{}}},{"type":"Feature","geometry":{"type":"Point","coordinates":["37.6173","55.755826"]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459200,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{}}},{"type":"Feature","geometry":{"type":"Point","coordinates":["37.6173","55.755826"]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459200,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{}}},{"type":"Feature","geometry":{"type":"Point","coordinates":["37.6173","55.755826"]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459200,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{}}},{"type":"Feature","geometry":{"type":"Point","coordinates":["37.6173","55.755826"]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459200,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{}}},{"type":"Feature","geometry":{"type":"Point","coordinates":["37.6173","55.755826"]},"properties":{"battery_status":"unplugged","ping":"MyString","battery":1,"tracker_id":"MyString","topic":"MyString","altitude":1,"longitude":"37.6173","velocity":"0","trigger":"background_event","bssid":"MyString","ssid":"MyString","connection":"wifi","vertical_accuracy":1,"accuracy":1,"timestamp":1609459200,"latitude":"55.755826","mode":1,"inrids":[],"in_regions":[],"city":null,"country":null,"geodata":{}}}]} diff --git a/spec/fixtures/files/watched/user@domain.com_gpx_track_single_segment.gpx b/spec/fixtures/files/watched/user@domain.com_gpx_track_single_segment.gpx new file mode 100644 index 00000000..c7447af0 --- /dev/null +++ b/spec/fixtures/files/watched/user@domain.com_gpx_track_single_segment.gpx @@ -0,0 +1,1239 @@ + + + + La Zubia - balcon + + Garmin Connect + + + + + La Zubia - balcon + + + 824.93 + + + + 822.91 + + + + 819.57 + + + + 815.2 + + + + 811.41 + + + + 808.11 + + + + 805.33 + + + + 802.85 + + + + 800.8 + + + + 798.9 + + + + 797.19 + + + + 795.8 + + + + 794.31 + + + + 793.25 + + + + 792.19 + + + + 791.44 + + + + 791.24 + + + + 791.47 + + + + 792.04 + + + + 792.18 + + + + 793.94 + + + + 795.29 + + + + 796.89 + + + + 798.7 + + + + 801.44 + + + + 803.97 + + + + 806.6 + + + + 809.27 + + + + 811.96 + + + + 814.62 + + + + 817.54 + + + + 820.18 + + + + 822.76 + + + + 825.25 + + + + 827.89 + + + + 830.82 + + + + 833.17 + + + + 835.42 + + + + 837.9 + + + + 839.89 + + + + 841.98 + + + + 844.17 + + + + 846.01 + + + + 847.32 + + + + 848.51 + + + + 849.54 + + + + 850.3 + + + + 850.74 + + + + 851.11 + + + + 851.31 + + + + 851.37 + + + + 851.36 + + + + 851.21 + + + + 851.04 + + + + 850.86 + + + + 850.41 + + + + 849.94 + + + + 849.54 + + + + 849.08 + + + + 848.67 + + + + 848.36 + + + + 848.08 + + + + 847.87 + + + + 847.77 + + + + 847.74 + + + + 847.75 + + + + 847.81 + + + + 847.96 + + + + 848.17 + + + + 848.37 + + + + 848.68 + + + + 849.01 + + + + 849.24 + + + + 849.47 + + + + 849.7 + + + + 849.88 + + + + 850.1 + + + + 850.25 + + + + 850.38 + + + + 850.47 + + + + 850.46 + + + + 850.35 + + + + 850.35 + + + + 850.02 + + + + 849.6 + + + + 849.05 + + + + 848.37 + + + + 847.54 + + + + 846.57 + + + + 845.55 + + + + 844.29 + + + + 842.85 + + + + 841.43 + + + + 839.98 + + + + 838.63 + + + + 837.18 + + + + 835.48 + + + + 833.92 + + + + 832.43 + + + + 831.06 + + + + 829.84 + + + + 829.04 + + + + 828.42 + + + + 828.15 + + + + 828.11 + + + + 828.51 + + + + 829.55 + + + + 830.31 + + + + 831.12 + + + + 831.93 + + + + 832.91 + + + + 833.85 + + + + 834.91 + + + + 836.07 + + + + 837.2 + + + + 838.38 + + + + 839.56 + + + + 840.58 + + + + 841.58 + + + + 842.46 + + + + 843.23 + + + + 843.46 + + + + 843.41 + + + + 842.64 + + + + 841.84 + + + + 840.81 + + + + 839.56 + + + + 837.86 + + + + 836.03 + + + + 833.91 + + + + 831.55 + + + + 828.71 + + + + 825.47 + + + + 820.96 + + + + 817.85 + + + + 814.71 + + + + 811.52 + + + + 808.25 + + + + 805.03 + + + + 801.68 + + + + 798.27 + + + + 794.91 + + + + 791.73 + + + + 788.61 + + + + 785.48 + + + + 782.4 + + + + 779.42 + + + + 776.47 + + + + 773.67 + + + + 770.99 + + + + 768.4 + + + + 765.66 + + + + 763.1 + + + + 760.26 + + + + 757.88 + + + + 755.75 + + + + 753.7 + + + + 751.75 + + + + 749.94 + + + + 748.17 + + + + 746.34 + + + + 744.47 + + + + 743.18 + + + + 742.0 + + + + 741.01 + + + + 740.17 + + + + 739.53 + + + + 738.88 + + + + 738.42 + + + + 738.16 + + + + 738.01 + + + + 738.01 + + + + 738.11 + + + + 738.36 + + + + 738.8 + + + + 739.13 + + + + 739.78 + + + + 740.12 + + + + 740.55 + + + + 740.93 + + + + 741.31 + + + + 741.6 + + + + 741.82 + + + + 741.89 + + + + 741.94 + + + + 741.89 + + + + 742.0 + + + + 742.05 + + + + 742.17 + + + + 742.28 + + + + 742.49 + + + + 742.74 + + + + 742.86 + + + + 743.34 + + + + 744.01 + + + + 744.96 + + + + 746.14 + + + + 747.41 + + + + 748.68 + + + + 750.03 + + + + 751.57 + + + + 753.47 + + + + 755.4 + + + + 757.49 + + + + 759.68 + + + + 762.09 + + + + 764.56 + + + + 767.4 + + + + 770.3 + + + + 773.45 + + + + 776.83 + + + + 780.51 + + + + 783.74 + + + + 786.94 + + + + 790.76 + + + + 794.06 + + + + 797.36 + + + + 800.75 + + + + 804.12 + + + + 807.53 + + + + 811.02 + + + + 814.61 + + + + 818.13 + + + + 821.6 + + + + 825.29 + + + + 828.89 + + + + 832.37 + + + + 836.28 + + + + 839.49 + + + + 842.19 + + + + 844.74 + + + + 847.21 + + + + 849.34 + + + + 851.3 + + + + 852.93 + + + + 854.35 + + + + 855.69 + + + + 856.86 + + + + 857.72 + + + + 858.43 + + + + 858.78 + + + + 859.01 + + + + 859.0 + + + + 858.97 + + + + 859.21 + + + + 859.45 + + + + 859.73 + + + + 860.06 + + + + 860.45 + + + + 861.08 + + + + 861.61 + + + + 862.29 + + + + 863.0 + + + + 863.9 + + + + 864.96 + + + + 866.07 + + + + 867.3 + + + + 869.0 + + + + 870.45 + + + + 871.97 + + + + 873.37 + + + + 874.8 + + + + 876.17 + + + + 877.6 + + + + 879.15 + + + + 880.87 + + + + 882.54 + + + + 884.28 + + + + 886.01 + + + + 887.84 + + + + 889.62 + + + + 891.29 + + + + 892.83 + + + + 893.87 + + + + 894.78 + + + + 895.66 + + + + 896.51 + + + + 896.83 + + + + 896.95 + + + + 896.98 + + + + 896.67 + + + + 896.92 + + + + 897.13 + + + + 897.08 + + + + 897.65 + + + + 898.62 + + + + 899.59 + + + + 900.3 + + + + 901.06 + + + + 901.98 + + + + 902.94 + + + + 904.14 + + + + 905.06 + + + + 905.5 + + + + 905.8 + + + + 905.47 + + + + 905.91 + + + + 906.01 + + + + 905.66 + + + + 904.85 + + + + 904.4 + + + + 903.49 + + + + 903.02 + + + + 901.8 + + + + 901.42 + + + + + diff --git a/spec/jobs/import_immich_geodata_job_spec.rb b/spec/jobs/import/immich_geodata_job_spec.rb similarity index 70% rename from spec/jobs/import_immich_geodata_job_spec.rb rename to spec/jobs/import/immich_geodata_job_spec.rb index 54eb09e5..c755852a 100644 --- a/spec/jobs/import_immich_geodata_job_spec.rb +++ b/spec/jobs/import/immich_geodata_job_spec.rb @@ -2,14 +2,14 @@ require 'rails_helper' -RSpec.describe ImportImmichGeodataJob, type: :job do +RSpec.describe Import::ImmichGeodataJob, type: :job do describe '#perform' do let(:user) { create(:user) } it 'calls Immich::ImportGeodata' do expect_any_instance_of(Immich::ImportGeodata).to receive(:call) - ImportImmichGeodataJob.perform_now(user.id) + described_class.perform_now(user.id) end end end diff --git a/spec/jobs/import/watcher_job_spec.rb b/spec/jobs/import/watcher_job_spec.rb new file mode 100644 index 00000000..2719601f --- /dev/null +++ b/spec/jobs/import/watcher_job_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Import::WatcherJob, type: :job do + describe '#perform' do + it 'calls Imports::Watcher' do + expect_any_instance_of(Imports::Watcher).to receive(:call) + + described_class.perform_now + end + end +end diff --git a/spec/services/imports/watcher_spec.rb b/spec/services/imports/watcher_spec.rb new file mode 100644 index 00000000..83dacdf2 --- /dev/null +++ b/spec/services/imports/watcher_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Imports::Watcher do + describe '#call' do + subject(:service) { described_class.new.call } + + let(:watched_dir_path) { Rails.root.join('spec/fixtures/files/watched') } + let(:user) { create(:user, email: 'user@domain.com') } + + before do + stub_const('Imports::Watcher::WATCHED_DIR_PATH', watched_dir_path) + end + + context 'when there are no files in the watched directory' do + it 'does not call ImportJob' do + expect(ImportJob).not_to receive(:perform_later) + + service + end + end + + context 'when there are files in the watched directory' do + Sidekiq::Testing.inline! do + context 'when the file has a valid user email' do + it 'creates an import for the user' do + expect { service }.to change(user.imports, :count).by(2) + end + end + + context 'when the file has an invalid user email' do + it 'does not create an import' do + expect { service }.not_to change(Import, :count) + end + end + + context 'when the import already exists' do + it 'does not create a new import' do + create(:import, user:, name: 'export_same_points.json') + create(:import, user:, name: 'gpx_track_single_segment.gpx') + + expect { service }.not_to change(Import, :count) + end + end + end + end + end +end diff --git a/spec/services/tasks/imports/google_records_spec.rb b/spec/services/tasks/imports/google_records_spec.rb index f160da0f..0310dbd1 100644 --- a/spec/services/tasks/imports/google_records_spec.rb +++ b/spec/services/tasks/imports/google_records_spec.rb @@ -7,8 +7,8 @@ RSpec.describe Tasks::Imports::GoogleRecords do let(:user) { create(:user) } let(:file_path) { Rails.root.join('spec/fixtures/files/google/records.json') } - it 'schedules the ImportGoogleTakeoutJob' do - expect(ImportGoogleTakeoutJob).to receive(:perform_later).exactly(3).times + it 'schedules the Import::GoogleTakeoutJob' do + expect(Import::GoogleTakeoutJob).to receive(:perform_later).exactly(3).times described_class.new(file_path, user.email).call end diff --git a/public/imports/.keep b/tmp/imports/watched/.keep similarity index 100% rename from public/imports/.keep rename to tmp/imports/watched/.keep diff --git a/tmp/imports/watched/put-your-files-here.txt b/tmp/imports/watched/put-your-files-here.txt new file mode 100644 index 00000000..4bd4ddd4 --- /dev/null +++ b/tmp/imports/watched/put-your-files-here.txt @@ -0,0 +1,5 @@ +The /public/imporst/watched/ directory is watched by Dawarich. Any files you put in this directory will be imported into the database. The name of the file must start with an email of the user you want to import the file for. The email must be followed by an underscore symbol (_) and the name of the file. + +For example, if you want to import a file for the user with the email address "email@dawarich.app", you would name the file "email@dawarich.app_2024-05-01_2024-05-31.gpx". The file will be imported into the database and the user will receive a notification in the app. + +Both GeoJSON and GPX files are supported.