mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
Compare commits
8 commits
bfeb936638
...
2425b2423a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2425b2423a | ||
|
|
43bc8c444c | ||
|
|
6b96e1f0be | ||
|
|
0dff80e12b | ||
|
|
58a7972976 | ||
|
|
cf50541be1 | ||
|
|
bc36882e73 | ||
|
|
e9eeb6aae2 |
23 changed files with 513 additions and 11 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -76,3 +76,10 @@ Makefile
|
||||||
/db/*.sqlite3
|
/db/*.sqlite3
|
||||||
/db/*.sqlite3-shm
|
/db/*.sqlite3-shm
|
||||||
/db/*.sqlite3-wal
|
/db/*.sqlite3-wal
|
||||||
|
|
||||||
|
# Playwright
|
||||||
|
node_modules/
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
|
||||||
# [0.29.2] - UNRELEASED
|
# [0.29.2] - 2025-07-12
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
|
|
@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
- Area popup styles are now more consistent.
|
- Area popup styles are now more consistent.
|
||||||
- Notification about Photon API load is now disabled.
|
- Notification about Photon API load is now disabled.
|
||||||
- All distance values are now stored in the database in meters. Conversion to user's preferred unit is done on the fly.
|
- All distance values are now stored in the database in meters. Conversion to user's preferred unit is done on the fly.
|
||||||
|
- Every night, Dawarich will try to fetch names for places and visits that don't have them. #1281 #902 #583 #212
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
//= link rails-ujs.js
|
||||||
//= link_tree ../images
|
//= link_tree ../images
|
||||||
//= link_directory ../stylesheets .css
|
//= link_directory ../stylesheets .css
|
||||||
//= link_tree ../builds
|
//= link_tree ../builds
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,6 @@ import "./channels"
|
||||||
|
|
||||||
import "trix"
|
import "trix"
|
||||||
import "@rails/actiontext"
|
import "@rails/actiontext"
|
||||||
|
|
||||||
|
import "@rails/ujs"
|
||||||
|
Rails.start()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AreaVisitsCalculatingJob < ApplicationJob
|
class AreaVisitsCalculatingJob < ApplicationJob
|
||||||
queue_as :default
|
queue_as :visit_suggesting
|
||||||
sidekiq_options retry: false
|
sidekiq_options retry: false
|
||||||
|
|
||||||
def perform(user_id)
|
def perform(user_id)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AreaVisitsCalculationSchedulingJob < ApplicationJob
|
class AreaVisitsCalculationSchedulingJob < ApplicationJob
|
||||||
queue_as :default
|
queue_as :visit_suggesting
|
||||||
sidekiq_options retry: false
|
sidekiq_options retry: false
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
|
|
|
||||||
11
app/jobs/places/bulk_name_fetching_job.rb
Normal file
11
app/jobs/places/bulk_name_fetching_job.rb
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Places::BulkNameFetchingJob < ApplicationJob
|
||||||
|
queue_as :places
|
||||||
|
|
||||||
|
def perform
|
||||||
|
Place.where(name: Place::DEFAULT_NAME).find_each do |place|
|
||||||
|
Places::NameFetchingJob.perform_later(place.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
11
app/jobs/places/name_fetching_job.rb
Normal file
11
app/jobs/places/name_fetching_job.rb
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Places::NameFetchingJob < ApplicationJob
|
||||||
|
queue_as :places
|
||||||
|
|
||||||
|
def perform(place_id)
|
||||||
|
place = Place.find(place_id)
|
||||||
|
|
||||||
|
Places::NameFetcher.new(place).call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -92,6 +92,9 @@ class Point < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def country_name
|
def country_name
|
||||||
|
# We have a country column in the database,
|
||||||
|
# but we also have a country_id column.
|
||||||
|
# TODO: rename country column to country_name
|
||||||
self.country&.name || read_attribute(:country) || ''
|
self.country&.name || read_attribute(:country) || ''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
35
app/services/places/name_fetcher.rb
Normal file
35
app/services/places/name_fetcher.rb
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Places
|
||||||
|
class NameFetcher
|
||||||
|
def initialize(place)
|
||||||
|
@place = place
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
geodata = Geocoder.search([@place.lat, @place.lon], units: :km, limit: 1, distance_sort: true).first
|
||||||
|
|
||||||
|
return if geodata.blank?
|
||||||
|
|
||||||
|
properties = geodata.data&.dig('properties')
|
||||||
|
return if properties.blank?
|
||||||
|
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
@place.name = properties['name'] if properties['name'].present?
|
||||||
|
@place.city = properties['city'] if properties['city'].present?
|
||||||
|
@place.country = properties['country'] if properties['country'].present?
|
||||||
|
@place.geodata = geodata.data if DawarichSettings.store_geodata?
|
||||||
|
@place.save!
|
||||||
|
|
||||||
|
if properties['name'].present?
|
||||||
|
@place
|
||||||
|
.visits
|
||||||
|
.where(name: Place::DEFAULT_NAME)
|
||||||
|
.update_all(name: properties['name'])
|
||||||
|
end
|
||||||
|
|
||||||
|
@place
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -124,7 +124,7 @@
|
||||||
<li><%= link_to 'Subscription', "#{MANAGER_URL}/auth/dawarich?token=#{current_user.generate_subscription_token}" %></li>
|
<li><%= link_to 'Subscription', "#{MANAGER_URL}/auth/dawarich?token=#{current_user.generate_subscription_token}" %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<li><%= link_to 'Logout', destroy_user_session_path, method: :delete, data: { turbo_method: :delete } %></li>
|
<li><%= link_to 'Logout', destroy_user_session_path, method: :delete, data: { turbo: false } %></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ pin_all_from 'app/javascript/channels', under: 'channels'
|
||||||
pin 'application', preload: true
|
pin 'application', preload: true
|
||||||
pin '@rails/actioncable', to: 'actioncable.esm.js'
|
pin '@rails/actioncable', to: 'actioncable.esm.js'
|
||||||
pin '@rails/activestorage', to: 'activestorage.esm.js'
|
pin '@rails/activestorage', to: 'activestorage.esm.js'
|
||||||
|
pin '@rails/ujs', to: 'rails-ujs.js'
|
||||||
pin '@hotwired/turbo-rails', to: 'turbo.min.js', preload: true
|
pin '@hotwired/turbo-rails', to: 'turbo.min.js', preload: true
|
||||||
pin '@hotwired/stimulus', to: 'stimulus.min.js', preload: true
|
pin '@hotwired/stimulus', to: 'stimulus.min.js', preload: true
|
||||||
pin '@hotwired/stimulus-loading', to: 'stimulus-loading.js', preload: true
|
pin '@hotwired/stimulus-loading', to: 'stimulus-loading.js', preload: true
|
||||||
|
|
|
||||||
|
|
@ -34,3 +34,8 @@ tracks_bulk_creating_job:
|
||||||
cron: "10 0 * * *" # every day at 00:10
|
cron: "10 0 * * *" # every day at 00:10
|
||||||
class: "Tracks::BulkCreatingJob"
|
class: "Tracks::BulkCreatingJob"
|
||||||
queue: tracks
|
queue: tracks
|
||||||
|
|
||||||
|
place_name_fetching_job:
|
||||||
|
cron: "30 0 * * *" # every day at 00:30
|
||||||
|
class: "Places::BulkNameFetchingJob"
|
||||||
|
queue: places
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,4 @@
|
||||||
- tracks
|
- tracks
|
||||||
- reverse_geocoding
|
- reverse_geocoding
|
||||||
- visit_suggesting
|
- visit_suggesting
|
||||||
|
- places
|
||||||
|
|
|
||||||
131
package-lock.json
generated
131
package-lock.json
generated
|
|
@ -12,6 +12,10 @@
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"trix": "^2.1.15"
|
"trix": "^2.1.15"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.54.1",
|
||||||
|
"@types/node": "^24.0.13"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18.17.1",
|
"node": "18.17.1",
|
||||||
"npm": "9.6.7"
|
"npm": "9.6.7"
|
||||||
|
|
@ -34,6 +38,22 @@
|
||||||
"@rails/actioncable": "^7.0"
|
"@rails/actioncable": "^7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@playwright/test": {
|
||||||
|
"version": "1.54.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz",
|
||||||
|
"integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright": "1.54.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rails/actioncable": {
|
"node_modules/@rails/actioncable": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rails/actioncable/-/actioncable-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rails/actioncable/-/actioncable-7.1.3.tgz",
|
||||||
|
|
@ -58,6 +78,16 @@
|
||||||
"spark-md5": "^3.0.1"
|
"spark-md5": "^3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "24.0.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.13.tgz",
|
||||||
|
"integrity": "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~7.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/trusted-types": {
|
"node_modules/@types/trusted-types": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
|
|
@ -133,6 +163,21 @@
|
||||||
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
|
||||||
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ=="
|
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/leaflet": {
|
"node_modules/leaflet": {
|
||||||
"version": "1.9.4",
|
"version": "1.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||||
|
|
@ -160,6 +205,38 @@
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/playwright": {
|
||||||
|
"version": "1.54.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz",
|
||||||
|
"integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.54.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright-core": {
|
||||||
|
"version": "1.54.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz",
|
||||||
|
"integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.3",
|
"version": "8.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||||
|
|
@ -226,6 +303,13 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dompurify": "^3.2.5"
|
"dompurify": "^3.2.5"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
|
||||||
|
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -243,6 +327,15 @@
|
||||||
"@rails/actioncable": "^7.0"
|
"@rails/actioncable": "^7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@playwright/test": {
|
||||||
|
"version": "1.54.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz",
|
||||||
|
"integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"playwright": "1.54.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@rails/actioncable": {
|
"@rails/actioncable": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rails/actioncable/-/actioncable-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rails/actioncable/-/actioncable-7.1.3.tgz",
|
||||||
|
|
@ -264,6 +357,15 @@
|
||||||
"spark-md5": "^3.0.1"
|
"spark-md5": "^3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"version": "24.0.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.13.tgz",
|
||||||
|
"integrity": "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"undici-types": "~7.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/trusted-types": {
|
"@types/trusted-types": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
|
|
@ -318,6 +420,13 @@
|
||||||
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
|
||||||
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ=="
|
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ=="
|
||||||
},
|
},
|
||||||
|
"fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"leaflet": {
|
"leaflet": {
|
||||||
"version": "1.9.4",
|
"version": "1.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||||
|
|
@ -333,6 +442,22 @@
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
||||||
},
|
},
|
||||||
|
"playwright": {
|
||||||
|
"version": "1.54.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz",
|
||||||
|
"integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"fsevents": "2.3.2",
|
||||||
|
"playwright-core": "1.54.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"playwright-core": {
|
||||||
|
"version": "1.54.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz",
|
||||||
|
"integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"postcss": {
|
"postcss": {
|
||||||
"version": "8.5.3",
|
"version": "8.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||||
|
|
@ -368,6 +493,12 @@
|
||||||
"requires": {
|
"requires": {
|
||||||
"dompurify": "^3.2.5"
|
"dompurify": "^3.2.5"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"undici-types": {
|
||||||
|
"version": "7.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
|
||||||
|
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
|
||||||
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,5 +10,10 @@
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18.17.1",
|
"node": "18.17.1",
|
||||||
"npm": "9.6.7"
|
"npm": "9.6.7"
|
||||||
}
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.54.1",
|
||||||
|
"@types/node": "^24.0.13"
|
||||||
|
},
|
||||||
|
"scripts": {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe AreaVisitsCalculationSchedulingJob, type: :job do
|
RSpec.describe AreaVisitsCalculationSchedulingJob, type: :job do
|
||||||
describe '#perform' do
|
describe '#perform' do
|
||||||
let(:area) { create(:area) }
|
let!(:user) { create(:user) }
|
||||||
let(:user) { create(:user) }
|
let!(:area) { create(:area, user: user) }
|
||||||
|
|
||||||
it 'calls the AreaVisitsCalculationService' do
|
it 'calls the AreaVisitsCalculationService' do
|
||||||
expect(AreaVisitsCalculatingJob).to receive(:perform_later).with(user.id).and_call_original
|
expect(AreaVisitsCalculatingJob).to receive(:perform_later).with(user.id)
|
||||||
|
|
||||||
described_class.new.perform
|
described_class.new.perform_now
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
26
spec/jobs/places/bulk_name_fetching_job_spec.rb
Normal file
26
spec/jobs/places/bulk_name_fetching_job_spec.rb
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Places::BulkNameFetchingJob, type: :job do
|
||||||
|
describe '#perform' do
|
||||||
|
let!(:place1) { create(:place, name: Place::DEFAULT_NAME) }
|
||||||
|
let!(:place2) { create(:place, name: Place::DEFAULT_NAME) }
|
||||||
|
let!(:place3) { create(:place, name: 'Other place') }
|
||||||
|
|
||||||
|
it 'enqueues name fetching job for each place with default name' do
|
||||||
|
expect { described_class.perform_now }.to \
|
||||||
|
have_enqueued_job(Places::NameFetchingJob).exactly(2).times
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not process places with custom names' do
|
||||||
|
expect { described_class.perform_now }.not_to \
|
||||||
|
have_enqueued_job(Places::NameFetchingJob).with(place3.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can be enqueued' do
|
||||||
|
expect { described_class.perform_later }.to have_enqueued_job(described_class)
|
||||||
|
.on_queue('places')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
29
spec/jobs/places/name_fetching_job_spec.rb
Normal file
29
spec/jobs/places/name_fetching_job_spec.rb
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Places::NameFetchingJob, type: :job do
|
||||||
|
describe '#perform' do
|
||||||
|
let(:place) { create(:place, name: Place::DEFAULT_NAME) }
|
||||||
|
let(:name_fetcher) { instance_double(Places::NameFetcher) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(Places::NameFetcher).to receive(:new).with(place).and_return(name_fetcher)
|
||||||
|
allow(name_fetcher).to receive(:call)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'finds the place and calls NameFetcher' do
|
||||||
|
expect(Place).to receive(:find).with(place.id).and_return(place)
|
||||||
|
expect(Places::NameFetcher).to receive(:new).with(place)
|
||||||
|
expect(name_fetcher).to receive(:call)
|
||||||
|
|
||||||
|
described_class.perform_now(place.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can be enqueued' do
|
||||||
|
expect { described_class.perform_later(place.id) }.to have_enqueued_job(described_class)
|
||||||
|
.with(place.id)
|
||||||
|
.on_queue('places')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -40,8 +40,10 @@ RSpec.configure do |config|
|
||||||
config.rswag_dry_run = false
|
config.rswag_dry_run = false
|
||||||
|
|
||||||
config.before(:suite) do
|
config.before(:suite) do
|
||||||
# Ensure Rails routes are loaded for Devise
|
|
||||||
Rails.application.reload_routes!
|
Rails.application.reload_routes!
|
||||||
|
|
||||||
|
# DatabaseCleaner.strategy = :transaction
|
||||||
|
# DatabaseCleaner.clean_with(:truncation)
|
||||||
end
|
end
|
||||||
|
|
||||||
config.before do
|
config.before do
|
||||||
|
|
@ -90,6 +92,12 @@ RSpec.configure do |config|
|
||||||
config.after(:suite) do
|
config.after(:suite) do
|
||||||
Rake::Task['rswag:generate'].invoke
|
Rake::Task['rswag:generate'].invoke
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# config.around(:each) do |example|
|
||||||
|
# DatabaseCleaner.cleaning do
|
||||||
|
# example.run
|
||||||
|
# end
|
||||||
|
# end
|
||||||
end
|
end
|
||||||
|
|
||||||
Shoulda::Matchers.configure do |config|
|
Shoulda::Matchers.configure do |config|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ RSpec.describe PointSerializer do
|
||||||
'inrids' => point.inrids,
|
'inrids' => point.inrids,
|
||||||
'in_regions' => point.in_regions,
|
'in_regions' => point.in_regions,
|
||||||
'city' => point.city,
|
'city' => point.city,
|
||||||
'country' => point.country,
|
'country' => point.read_attribute(:country),
|
||||||
'geodata' => point.geodata,
|
'geodata' => point.geodata,
|
||||||
'course' => point.course,
|
'course' => point.course,
|
||||||
'course_accuracy' => point.course_accuracy,
|
'course_accuracy' => point.course_accuracy,
|
||||||
|
|
|
||||||
220
spec/services/places/name_fetcher_spec.rb
Normal file
220
spec/services/places/name_fetcher_spec.rb
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Places::NameFetcher do
|
||||||
|
describe '#call' do
|
||||||
|
subject(:service) { described_class.new(place) }
|
||||||
|
|
||||||
|
let(:place) do
|
||||||
|
create(
|
||||||
|
:place,
|
||||||
|
name: Place::DEFAULT_NAME,
|
||||||
|
city: nil,
|
||||||
|
country: nil,
|
||||||
|
geodata: {},
|
||||||
|
lonlat: 'POINT(10.0 10.0)'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:geocoder_result) do
|
||||||
|
double(
|
||||||
|
'geocoder_result',
|
||||||
|
data: {
|
||||||
|
'properties' => {
|
||||||
|
'name' => 'Central Park',
|
||||||
|
'city' => 'New York',
|
||||||
|
'country' => 'United States'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(Geocoder).to receive(:search).and_return([geocoder_result])
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when geocoding is successful' do
|
||||||
|
it 'calls Geocoder with correct parameters' do
|
||||||
|
expect(Geocoder).to receive(:search)
|
||||||
|
.with([place.lat, place.lon], units: :km, limit: 1, distance_sort: true)
|
||||||
|
.and_return([geocoder_result])
|
||||||
|
|
||||||
|
service.call
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates place name from geocoder data' do
|
||||||
|
expect { service.call }.to change(place, :name)
|
||||||
|
.from(Place::DEFAULT_NAME)
|
||||||
|
.to('Central Park')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates place city from geocoder data' do
|
||||||
|
expect { service.call }.to change(place, :city)
|
||||||
|
.from(nil)
|
||||||
|
.to('New York')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates place country from geocoder data' do
|
||||||
|
expect { service.call }.to change(place, :country)
|
||||||
|
.from(nil)
|
||||||
|
.to('United States')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'saves the place' do
|
||||||
|
expect(place).to receive(:save!)
|
||||||
|
|
||||||
|
service.call
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when DawarichSettings.store_geodata? is enabled' do
|
||||||
|
before do
|
||||||
|
allow(DawarichSettings).to receive(:store_geodata?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'stores geodata in the place' do
|
||||||
|
expect { service.call }.to change(place, :geodata)
|
||||||
|
.from({})
|
||||||
|
.to(geocoder_result.data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when DawarichSettings.store_geodata? is disabled' do
|
||||||
|
before do
|
||||||
|
allow(DawarichSettings).to receive(:store_geodata?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not store geodata in the place' do
|
||||||
|
expect { service.call }.not_to change(place, :geodata)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when place has visits with default name' do
|
||||||
|
let!(:visit_with_default_name) do
|
||||||
|
create(:visit, name: Place::DEFAULT_NAME)
|
||||||
|
end
|
||||||
|
let!(:visit_with_custom_name) do
|
||||||
|
create(:visit, name: 'Custom Visit Name')
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
place.visits << visit_with_default_name
|
||||||
|
place.visits << visit_with_custom_name
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates visits with default name to the new place name' do
|
||||||
|
expect { service.call }.to \
|
||||||
|
change { visit_with_default_name.reload.name }
|
||||||
|
.from(Place::DEFAULT_NAME)
|
||||||
|
.to('Central Park')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not update visits with custom names' do
|
||||||
|
expect { service.call }.not_to \
|
||||||
|
change { visit_with_custom_name.reload.name }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when using transactions' do
|
||||||
|
it 'wraps updates in a transaction' do
|
||||||
|
expect(ActiveRecord::Base).to \
|
||||||
|
receive(:transaction).and_call_original
|
||||||
|
|
||||||
|
service.call
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'rolls back changes if save fails' do
|
||||||
|
allow(place).to receive(:save!).and_raise(ActiveRecord::RecordInvalid)
|
||||||
|
|
||||||
|
expect { service.call }.to raise_error(ActiveRecord::RecordInvalid)
|
||||||
|
expect(place.reload.name).to eq(Place::DEFAULT_NAME)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the updated place' do
|
||||||
|
result = service.call
|
||||||
|
expect(result).to eq(place)
|
||||||
|
expect(result.name).to eq('Central Park')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when geocoding returns no results' do
|
||||||
|
before do
|
||||||
|
allow(Geocoder).to receive(:search).and_return([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns nil' do
|
||||||
|
expect(service.call).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not update the place' do
|
||||||
|
expect { service.call }.not_to change(place, :name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not call save on the place' do
|
||||||
|
expect(place).not_to receive(:save!)
|
||||||
|
|
||||||
|
service.call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when geocoding returns nil result' do
|
||||||
|
before do
|
||||||
|
allow(Geocoder).to receive(:search).and_return([nil])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns nil' do
|
||||||
|
expect(service.call).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not update the place' do
|
||||||
|
expect { service.call }.not_to change(place, :name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when geocoder result has missing properties' do
|
||||||
|
let(:incomplete_geocoder_result) do
|
||||||
|
double(
|
||||||
|
'geocoder_result',
|
||||||
|
data: {
|
||||||
|
'properties' => {
|
||||||
|
'name' => 'Partial Place',
|
||||||
|
'city' => nil,
|
||||||
|
'country' => 'United States'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(Geocoder).to receive(:search).and_return([incomplete_geocoder_result])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates place with available data' do
|
||||||
|
service.call
|
||||||
|
|
||||||
|
expect(place.name).to eq('Partial Place')
|
||||||
|
expect(place.city).to be_nil
|
||||||
|
expect(place.country).to eq('United States')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when geocoder result has no properties' do
|
||||||
|
let(:no_properties_result) do
|
||||||
|
double('geocoder_result', data: {})
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(Geocoder).to receive(:search).and_return([no_properties_result])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles missing properties gracefully' do
|
||||||
|
expect { service.call }.not_to raise_error
|
||||||
|
|
||||||
|
expect(place.name).to eq(Place::DEFAULT_NAME)
|
||||||
|
expect(place.city).to be_nil
|
||||||
|
expect(place.country).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
4
vendor/javascript/@rails--ujs.js
vendored
Normal file
4
vendor/javascript/@rails--ujs.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue