diff --git a/.gitignore b/.gitignore
index 3dd92eba..91bd95ac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,16 +24,23 @@
/tmp/storage/*
!/tmp/storage/
!/tmp/storage/.keep
+/tmp/imports/*
+!/tmp/imports/
+!/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
-!/public/imports/watched/.keep
-!/public/imports/watched/put-your-files-here.txt
# Ignore master key for decrypting credentials and more.
/config/master.key
diff --git a/app/jobs/import/watcher_job.rb b/app/jobs/import/watcher_job.rb
new file mode 100644
index 00000000..37881664
--- /dev/null
+++ b/app/jobs/import/watcher_job.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Import::WatcherJob < ApplicationJob
+ queue_as :imports
+
+ def perform
+ Imports::Watcher.new.call
+ end
+end
diff --git a/app/services/imports/watcher.rb b/app/services/imports/watcher.rb
new file mode 100644
index 00000000..d5d95dd9
--- /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_path 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/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/watcher_job_spec.rb b/spec/jobs/import/watcher_job_spec.rb
new file mode 100644
index 00000000..b7efa1da
--- /dev/null
+++ b/spec/jobs/import/watcher_job_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Import::WatcherJob, type: :job do
+ pending "add some examples to (or delete) #{__FILE__}"
+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/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/aa@bb.com-gpx_track_single_segment.gpx b/tmp/imports/watched/aa@bb.com-gpx_track_single_segment.gpx
new file mode 100644
index 00000000..c7447af0
--- /dev/null
+++ b/tmp/imports/watched/aa@bb.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/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.