Implement creating imports from watched directory

This commit is contained in:
Eugene Burmakin 2024-10-03 15:08:23 +02:00
parent 74ec1d65f9
commit d4e2e5006e
10 changed files with 2623 additions and 3 deletions

13
.gitignore vendored
View file

@ -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

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
class Import::WatcherJob < ApplicationJob
queue_as :imports
def perform
Imports::Watcher.new.call
end
end

View file

@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe Import::WatcherJob, type: :job do
pending "add some examples to (or delete) #{__FILE__}"
end

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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.