mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
Compare commits
9 commits
f60944d8d5
...
73c09a09fd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73c09a09fd | ||
|
|
b4c2def2be | ||
|
|
8a1e42a2e8 | ||
|
|
2f11003c29 | ||
|
|
8d2ade1bdc | ||
|
|
7f277612fc | ||
|
|
2f5487cd35 | ||
|
|
5455228b80 | ||
|
|
07ee46bce3 |
23 changed files with 79 additions and 119 deletions
|
|
@ -1 +1 @@
|
||||||
0.36.5
|
0.37.1
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,14 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
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.36.5] - Unreleased
|
# [0.37.1] - 2025-12-30
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- The db migration preventing the app from starting.
|
||||||
|
- Raw data archive verifier now allows having points deleted from the db after archiving.
|
||||||
|
|
||||||
|
# [0.37.0] - 2025-12-30
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class BulkVisitsSuggestingJob < ApplicationJob
|
||||||
|
|
||||||
users.active.find_each do |user|
|
users.active.find_each do |user|
|
||||||
next unless user.safe_settings.visits_suggestions_enabled?
|
next unless user.safe_settings.visits_suggestions_enabled?
|
||||||
next unless user.points_count.positive?
|
next unless user.points_count&.positive?
|
||||||
|
|
||||||
schedule_chunked_jobs(user, time_chunks)
|
schedule_chunked_jobs(user, time_chunks)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ class Tracks::DailyGenerationJob < ApplicationJob
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
User.active_or_trial.find_each do |user|
|
User.active_or_trial.find_each do |user|
|
||||||
next if user.points_count.zero?
|
next if user.points_count&.zero?
|
||||||
|
|
||||||
process_user_daily_tracks(user)
|
process_user_daily_tracks(user)
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ class StatsQuery
|
||||||
end
|
end
|
||||||
|
|
||||||
{
|
{
|
||||||
total: user.points_count,
|
total: user.points_count.to_i,
|
||||||
geocoded: cached_stats[:geocoded],
|
geocoded: cached_stats[:geocoded],
|
||||||
without_data: cached_stats[:without_data]
|
without_data: cached_stats[:without_data]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ class Imports::Destroy
|
||||||
end
|
end
|
||||||
|
|
||||||
def call
|
def call
|
||||||
points_count = @import.points_count
|
points_count = @import.points_count.to_i
|
||||||
|
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
@import.points.destroy_all
|
@import.points.destroy_all
|
||||||
|
|
|
||||||
|
|
@ -110,18 +110,24 @@ module Points
|
||||||
return { success: false, error: 'Point IDs checksum mismatch' }
|
return { success: false, error: 'Point IDs checksum mismatch' }
|
||||||
end
|
end
|
||||||
|
|
||||||
# 8. Verify all points still exist in database
|
# 8. Check which points still exist in database (informational only)
|
||||||
existing_count = Point.where(id: point_ids).count
|
existing_count = Point.where(id: point_ids).count
|
||||||
if existing_count != point_ids.count
|
if existing_count != point_ids.count
|
||||||
return {
|
Rails.logger.info(
|
||||||
success: false,
|
"Archive #{archive.id}: #{point_ids.count - existing_count} points no longer in database " \
|
||||||
error: "Missing points in database: expected #{point_ids.count}, found #{existing_count}"
|
"(#{existing_count}/#{point_ids.count} remaining). This is OK if user deleted their data."
|
||||||
}
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# 9. Verify archived raw_data matches current database raw_data
|
# 9. Verify archived raw_data matches current database raw_data (only for existing points)
|
||||||
verification_result = verify_raw_data_matches(archived_data)
|
if existing_count.positive?
|
||||||
return verification_result unless verification_result[:success]
|
verification_result = verify_raw_data_matches(archived_data)
|
||||||
|
return verification_result unless verification_result[:success]
|
||||||
|
else
|
||||||
|
Rails.logger.info(
|
||||||
|
"Archive #{archive.id}: Skipping raw_data verification - no points remain in database"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
{ success: true }
|
{ success: true }
|
||||||
end
|
end
|
||||||
|
|
@ -149,11 +155,18 @@ module Points
|
||||||
point_ids_to_check = archived_data.keys.sample(100)
|
point_ids_to_check = archived_data.keys.sample(100)
|
||||||
end
|
end
|
||||||
|
|
||||||
mismatches = []
|
# Filter to only check points that still exist in the database
|
||||||
found_points = 0
|
existing_point_ids = Point.where(id: point_ids_to_check).pluck(:id)
|
||||||
|
|
||||||
|
if existing_point_ids.empty?
|
||||||
|
# No points remain to verify, but that's OK
|
||||||
|
Rails.logger.info("No points remaining to verify raw_data matches")
|
||||||
|
return { success: true }
|
||||||
|
end
|
||||||
|
|
||||||
Point.where(id: point_ids_to_check).find_each do |point|
|
mismatches = []
|
||||||
found_points += 1
|
|
||||||
|
Point.where(id: existing_point_ids).find_each do |point|
|
||||||
archived_raw_data = archived_data[point.id]
|
archived_raw_data = archived_data[point.id]
|
||||||
current_raw_data = point.raw_data
|
current_raw_data = point.raw_data
|
||||||
|
|
||||||
|
|
@ -167,14 +180,6 @@ module Points
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Check if we found all the points we were looking for
|
|
||||||
if found_points != point_ids_to_check.size
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "Missing points during data verification: expected #{point_ids_to_check.size}, found #{found_points}"
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
if mismatches.any?
|
if mismatches.any?
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ class PointsLimitExceeded
|
||||||
return false if DawarichSettings.self_hosted?
|
return false if DawarichSettings.self_hosted?
|
||||||
|
|
||||||
Rails.cache.fetch(cache_key, expires_in: 1.day) do
|
Rails.cache.fetch(cache_key, expires_in: 1.day) do
|
||||||
@user.points_count >= points_limit
|
@user.points_count.to_i >= points_limit
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -323,7 +323,7 @@ class Users::ExportData
|
||||||
trips: user.trips.count,
|
trips: user.trips.count,
|
||||||
stats: user.stats.count,
|
stats: user.stats.count,
|
||||||
notifications: user.notifications.count,
|
notifications: user.notifications.count,
|
||||||
points: user.points_count,
|
points: user.points_count.to_i,
|
||||||
visits: user.visits.count,
|
visits: user.visits.count,
|
||||||
places: user.visited_places.count
|
places: user.visited_places.count
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -219,9 +219,7 @@ class Users::ImportData::Points
|
||||||
country_key = [country_info['name'], country_info['iso_a2'], country_info['iso_a3']]
|
country_key = [country_info['name'], country_info['iso_a2'], country_info['iso_a3']]
|
||||||
country = countries_lookup[country_key]
|
country = countries_lookup[country_key]
|
||||||
|
|
||||||
if country.nil? && country_info['name'].present?
|
country = countries_lookup[country_info['name']] if country.nil? && country_info['name'].present?
|
||||||
country = countries_lookup[country_info['name']]
|
|
||||||
end
|
|
||||||
|
|
||||||
if country
|
if country
|
||||||
attributes['country_id'] = country.id
|
attributes['country_id'] = country.id
|
||||||
|
|
@ -254,12 +252,12 @@ class Users::ImportData::Points
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_lonlat_field(attributes, point_data)
|
def ensure_lonlat_field(attributes, point_data)
|
||||||
if attributes['lonlat'].blank? && point_data['longitude'].present? && point_data['latitude'].present?
|
return unless attributes['lonlat'].blank? && point_data['longitude'].present? && point_data['latitude'].present?
|
||||||
longitude = point_data['longitude'].to_f
|
|
||||||
latitude = point_data['latitude'].to_f
|
longitude = point_data['longitude'].to_f
|
||||||
attributes['lonlat'] = "POINT(#{longitude} #{latitude})"
|
latitude = point_data['latitude'].to_f
|
||||||
logger.debug "Reconstructed lonlat: #{attributes['lonlat']}"
|
attributes['lonlat'] = "POINT(#{longitude} #{latitude})"
|
||||||
end
|
logger.debug "Reconstructed lonlat: #{attributes['lonlat']}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize_timestamp_for_lookup(timestamp)
|
def normalize_timestamp_for_lookup(timestamp)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<p class="py-6">
|
<p class="py-6">
|
||||||
<p class='py-2'>
|
<p class='py-2'>
|
||||||
You have used <%= number_with_delimiter(current_user.points_count) %> points of <%= number_with_delimiter(DawarichSettings::BASIC_PAID_PLAN_LIMIT) %> available.
|
You have used <%= number_with_delimiter(current_user.points_count.to_i) %> points of <%= number_with_delimiter(DawarichSettings::BASIC_PAID_PLAN_LIMIT) %> available.
|
||||||
</p>
|
</p>
|
||||||
<progress class="progress progress-primary w-1/2 h-5" value="<%= current_user.points_count %>" max="<%= DawarichSettings::BASIC_PAID_PLAN_LIMIT %>"></progress>
|
<progress class="progress progress-primary w-1/2 h-5" value="<%= current_user.points_count.to_i %>" max="<%= DawarichSettings::BASIC_PAID_PLAN_LIMIT %>"></progress>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<%= number_with_delimiter user.points_count %>
|
<%= number_with_delimiter user.points_count.to_i %>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<%= human_datetime(user.created_at) %>
|
<%= human_datetime(user.created_at) %>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="hero-content text-center py-12">
|
<div class="hero-content text-center py-12">
|
||||||
<div class="max-w-lg">
|
<div class="max-w-lg">
|
||||||
<h1 class="text-4xl font-bold"><%= @digest.year %> Year in Review</h1>
|
<h1 class="text-4xl font-bold"><%= @digest.year %> Year in Review</h1>
|
||||||
<p class="py-4">A journey, by the numbers</p>
|
<p class="py-4">Your journey, by the numbers</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<p>
|
<p>
|
||||||
Hi, this is Evgenii from Dawarich! Pretty wild journey last yeah, huh? Let's take a look back at all the places you explored in <strong><%= @digest.year %></strong>.
|
Hi, this is Evgenii from Dawarich! Pretty wild journey last year, huh? Let's take a look back at all the places you explored in <strong><%= @digest.year %></strong>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
<div class="group relative timeline-box">
|
<div class="group relative timeline-box">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div class="opacity-100 flex items-center mr-4">
|
||||||
<%= render 'visits/name', visit: visit %>
|
|
||||||
<div><%= "#{visit.started_at.strftime('%H:%M')} - #{visit.ended_at.strftime('%H:%M')}" %></div>
|
|
||||||
</div>
|
|
||||||
<div class="opacity-0 transition-opacity duration-200 group-hover:opacity-100 flex items-center ml-4">
|
|
||||||
<%= render 'visits/buttons', visit: visit %>
|
<%= render 'visits/buttons', visit: visit %>
|
||||||
<!-- The button to open modal -->
|
<!-- The button to open modal -->
|
||||||
<label for="visit_details_popup_<%= visit.id %>" class='btn btn-xs btn-info'>Map</label>
|
<label for="visit_details_popup_<%= visit.id %>" class='btn btn-xs btn-info'>Map</label>
|
||||||
|
|
||||||
<%= render 'visits/modal', visit: visit %>
|
<%= render 'visits/modal', visit: visit %>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<%= render 'visits/name', visit: visit %>
|
||||||
|
<div class="lg:text-left"><%= "#{visit.started_at.strftime('%H:%M')} - #{visit.ended_at.strftime('%H:%M')}" %></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,5 @@ module Dawarich
|
||||||
config.active_job.queue_adapter = :sidekiq
|
config.active_job.queue_adapter = :sidekiq
|
||||||
|
|
||||||
config.action_mailer.preview_paths << Rails.root.join('spec/mailers/previews').to_s
|
config.action_mailer.preview_paths << Rails.root.join('spec/mailers/previews').to_s
|
||||||
|
|
||||||
config.middleware.use Rack::Deflater
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,8 @@
|
||||||
|
|
||||||
class AddVisitedCountriesToTrips < ActiveRecord::Migration[8.0]
|
class AddVisitedCountriesToTrips < ActiveRecord::Migration[8.0]
|
||||||
def change
|
def change
|
||||||
# safety_assured do
|
execute <<-SQL
|
||||||
execute <<-SQL
|
|
||||||
ALTER TABLE trips ADD COLUMN visited_countries JSONB DEFAULT '{}'::jsonb NOT NULL;
|
ALTER TABLE trips ADD COLUMN visited_countries JSONB DEFAULT '{}'::jsonb NOT NULL;
|
||||||
SQL
|
SQL
|
||||||
# end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,8 @@ class AddH3HexIdsToStats < ActiveRecord::Migration[8.0]
|
||||||
|
|
||||||
def change
|
def change
|
||||||
add_column :stats, :h3_hex_ids, :jsonb, default: {}, if_not_exists: true
|
add_column :stats, :h3_hex_ids, :jsonb, default: {}, if_not_exists: true
|
||||||
# safety_assured do
|
add_index :stats, :h3_hex_ids, using: :gin,
|
||||||
add_index :stats, :h3_hex_ids, using: :gin,
|
where: "(h3_hex_ids IS NOT NULL AND h3_hex_ids != '{}'::jsonb)",
|
||||||
where: "(h3_hex_ids IS NOT NULL AND h3_hex_ids != '{}'::jsonb)",
|
algorithm: :concurrently, if_not_exists: true
|
||||||
algorithm: :concurrently, if_not_exists: true
|
|
||||||
# end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ class CreatePointsRawDataArchives < ActiveRecord::Migration[8.0]
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index :points_raw_data_archives, :user_id
|
add_index :points_raw_data_archives, :user_id
|
||||||
add_index :points_raw_data_archives, [:user_id, :year, :month]
|
add_index :points_raw_data_archives, %i[user_id year month]
|
||||||
add_index :points_raw_data_archives, :archived_at
|
add_index :points_raw_data_archives, :archived_at
|
||||||
add_foreign_key :points_raw_data_archives, :users, validate: false
|
add_foreign_key :points_raw_data_archives, :users, validate: false
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -27,15 +27,19 @@ class AddCompositeIndexToStats < ActiveRecord::Migration[8.0]
|
||||||
deleted_count = 0
|
deleted_count = 0
|
||||||
loop do
|
loop do
|
||||||
batch_deleted = execute(<<-SQL.squish).cmd_tuples
|
batch_deleted = execute(<<-SQL.squish).cmd_tuples
|
||||||
DELETE FROM stats s1
|
DELETE FROM stats
|
||||||
WHERE EXISTS (
|
WHERE id IN (
|
||||||
SELECT 1 FROM stats s2
|
SELECT s1.id
|
||||||
WHERE s2.user_id = s1.user_id
|
FROM stats s1
|
||||||
AND s2.year = s1.year
|
WHERE EXISTS (
|
||||||
AND s2.month = s1.month
|
SELECT 1 FROM stats s2
|
||||||
AND s2.id > s1.id
|
WHERE s2.user_id = s1.user_id
|
||||||
|
AND s2.year = s1.year
|
||||||
|
AND s2.month = s1.month
|
||||||
|
AND s2.id > s1.id
|
||||||
|
)
|
||||||
|
LIMIT #{BATCH_SIZE}
|
||||||
)
|
)
|
||||||
LIMIT #{BATCH_SIZE}
|
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
break if batch_deleted.zero?
|
break if batch_deleted.zero?
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AddVerifiedAtToPointsRawDataArchives < ActiveRecord::Migration[8.0]
|
class AddVerifiedAtToPointsRawDataArchives < ActiveRecord::Migration[8.0]
|
||||||
def change
|
def change
|
||||||
add_column :points_raw_data_archives, :verified_at, :datetime
|
add_column :points_raw_data_archives, :verified_at, :datetime
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ class ChangeDigestsDistanceToBigint < ActiveRecord::Migration[8.0]
|
||||||
disable_ddl_transaction!
|
disable_ddl_transaction!
|
||||||
|
|
||||||
def up
|
def up
|
||||||
safety_assured { change_column :digests, :distance, :bigint, null: false, default: 0 }
|
change_column :digests, :distance, :bigint, null: false, default: 0
|
||||||
end
|
end
|
||||||
|
|
||||||
def down
|
def down
|
||||||
safety_assured { change_column :digests, :distance, :integer, null: false, default: 0 }
|
change_column :digests, :distance, :integer, null: false, default: 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe 'devise/shared/_links.html.erb', type: :view do
|
|
||||||
let(:resource_name) { :user }
|
|
||||||
let(:devise_mapping) { Devise.mappings[:user] }
|
|
||||||
|
|
||||||
before do
|
|
||||||
def view.resource_name
|
|
||||||
:user
|
|
||||||
end
|
|
||||||
|
|
||||||
def view.devise_mapping
|
|
||||||
Devise.mappings[:user]
|
|
||||||
end
|
|
||||||
|
|
||||||
def view.resource_class
|
|
||||||
User
|
|
||||||
end
|
|
||||||
|
|
||||||
def view.signed_in?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with OIDC provider' do
|
|
||||||
before do
|
|
||||||
stub_const('OMNIAUTH_PROVIDERS', [:openid_connect])
|
|
||||||
allow(User).to receive(:omniauth_providers).and_return([:openid_connect])
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'displays custom OIDC provider name' do
|
|
||||||
stub_const('OIDC_PROVIDER_NAME', 'Authentik')
|
|
||||||
|
|
||||||
render
|
|
||||||
|
|
||||||
expect(rendered).to have_button('Sign in with Authentik')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'displays default name when OIDC_PROVIDER_NAME is not set' do
|
|
||||||
stub_const('OIDC_PROVIDER_NAME', 'Openid Connect')
|
|
||||||
|
|
||||||
render
|
|
||||||
|
|
||||||
expect(rendered).to have_button('Sign in with Openid Connect')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
Loading…
Reference in a new issue