Compare commits

..

1 commit

Author SHA1 Message Date
Alex George
9951841329
Merge 4f87fb7220 into 26062a1278 2025-12-29 09:30:50 -05:00
22 changed files with 113 additions and 73 deletions

View file

@ -1 +1 @@
0.37.1
0.36.5

View file

@ -4,14 +4,7 @@ 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.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
# [0.36.5] - Unreleased
## Added

View file

@ -18,7 +18,7 @@ class BulkVisitsSuggestingJob < ApplicationJob
users.active.find_each do |user|
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)
end

View file

@ -21,7 +21,7 @@ class Tracks::DailyGenerationJob < ApplicationJob
def perform
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)
rescue StandardError => e

View file

@ -11,7 +11,7 @@ class StatsQuery
end
{
total: user.points_count.to_i,
total: user.points_count,
geocoded: cached_stats[:geocoded],
without_data: cached_stats[:without_data]
}

View file

@ -9,7 +9,7 @@ class Imports::Destroy
end
def call
points_count = @import.points_count.to_i
points_count = @import.points_count
ActiveRecord::Base.transaction do
@import.points.destroy_all

View file

@ -110,24 +110,18 @@ module Points
return { success: false, error: 'Point IDs checksum mismatch' }
end
# 8. Check which points still exist in database (informational only)
# 8. Verify all points still exist in database
existing_count = Point.where(id: point_ids).count
if existing_count != point_ids.count
Rails.logger.info(
"Archive #{archive.id}: #{point_ids.count - existing_count} points no longer in database " \
"(#{existing_count}/#{point_ids.count} remaining). This is OK if user deleted their data."
)
return {
success: false,
error: "Missing points in database: expected #{point_ids.count}, found #{existing_count}"
}
end
# 9. Verify archived raw_data matches current database raw_data (only for existing points)
if existing_count.positive?
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
# 9. Verify archived raw_data matches current database raw_data
verification_result = verify_raw_data_matches(archived_data)
return verification_result unless verification_result[:success]
{ success: true }
end
@ -155,18 +149,11 @@ module Points
point_ids_to_check = archived_data.keys.sample(100)
end
# Filter to only check points that still exist in the database
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
mismatches = []
found_points = 0
Point.where(id: existing_point_ids).find_each do |point|
Point.where(id: point_ids_to_check).find_each do |point|
found_points += 1
archived_raw_data = archived_data[point.id]
current_raw_data = point.raw_data
@ -180,6 +167,14 @@ module Points
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?
return {
success: false,

View file

@ -9,7 +9,7 @@ class PointsLimitExceeded
return false if DawarichSettings.self_hosted?
Rails.cache.fetch(cache_key, expires_in: 1.day) do
@user.points_count.to_i >= points_limit
@user.points_count >= points_limit
end
end

View file

@ -323,7 +323,7 @@ class Users::ExportData
trips: user.trips.count,
stats: user.stats.count,
notifications: user.notifications.count,
points: user.points_count.to_i,
points: user.points_count,
visits: user.visits.count,
places: user.visited_places.count
}

View file

@ -219,7 +219,9 @@ class Users::ImportData::Points
country_key = [country_info['name'], country_info['iso_a2'], country_info['iso_a3']]
country = countries_lookup[country_key]
country = countries_lookup[country_info['name']] if country.nil? && country_info['name'].present?
if country.nil? && country_info['name'].present?
country = countries_lookup[country_info['name']]
end
if country
attributes['country_id'] = country.id
@ -252,12 +254,12 @@ class Users::ImportData::Points
end
def ensure_lonlat_field(attributes, point_data)
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
attributes['lonlat'] = "POINT(#{longitude} #{latitude})"
logger.debug "Reconstructed lonlat: #{attributes['lonlat']}"
if attributes['lonlat'].blank? && point_data['longitude'].present? && point_data['latitude'].present?
longitude = point_data['longitude'].to_f
latitude = point_data['latitude'].to_f
attributes['lonlat'] = "POINT(#{longitude} #{latitude})"
logger.debug "Reconstructed lonlat: #{attributes['lonlat']}"
end
end
def normalize_timestamp_for_lookup(timestamp)

View file

@ -1,6 +1,6 @@
<p class="py-6">
<p class='py-2'>
You have used <%= number_with_delimiter(current_user.points_count.to_i) %> points of <%= number_with_delimiter(DawarichSettings::BASIC_PAID_PLAN_LIMIT) %> available.
You have used <%= number_with_delimiter(current_user.points_count) %> points of <%= number_with_delimiter(DawarichSettings::BASIC_PAID_PLAN_LIMIT) %> available.
</p>
<progress class="progress progress-primary w-1/2 h-5" value="<%= current_user.points_count.to_i %>" max="<%= DawarichSettings::BASIC_PAID_PLAN_LIMIT %>"></progress>
<progress class="progress progress-primary w-1/2 h-5" value="<%= current_user.points_count %>" max="<%= DawarichSettings::BASIC_PAID_PLAN_LIMIT %>"></progress>
</p>

View file

@ -24,7 +24,7 @@
</div>
</td>
<td>
<%= number_with_delimiter user.points_count.to_i %>
<%= number_with_delimiter user.points_count %>
</td>
<td>
<%= human_datetime(user.created_at) %>

View file

@ -4,7 +4,7 @@
<div class="hero-content text-center py-12">
<div class="max-w-lg">
<h1 class="text-4xl font-bold"><%= @digest.year %> Year in Review</h1>
<p class="py-4">Your journey, by the numbers</p>
<p class="py-4">A journey, by the numbers</p>
</div>
</div>
</div>

View file

@ -168,7 +168,7 @@
<div class="content">
<p>
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>.
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>.
</p>
</div>

View file

@ -38,5 +38,7 @@ module Dawarich
config.active_job.queue_adapter = :sidekiq
config.action_mailer.preview_paths << Rails.root.join('spec/mailers/previews').to_s
config.middleware.use Rack::Deflater
end
end

View file

@ -2,8 +2,10 @@
class AddVisitedCountriesToTrips < ActiveRecord::Migration[8.0]
def change
execute <<-SQL
# safety_assured do
execute <<-SQL
ALTER TABLE trips ADD COLUMN visited_countries JSONB DEFAULT '{}'::jsonb NOT NULL;
SQL
SQL
# end
end
end

View file

@ -5,8 +5,10 @@ class AddH3HexIdsToStats < ActiveRecord::Migration[8.0]
def change
add_column :stats, :h3_hex_ids, :jsonb, default: {}, if_not_exists: true
add_index :stats, :h3_hex_ids, using: :gin,
where: "(h3_hex_ids IS NOT NULL AND h3_hex_ids != '{}'::jsonb)",
algorithm: :concurrently, if_not_exists: true
# safety_assured do
add_index :stats, :h3_hex_ids, using: :gin,
where: "(h3_hex_ids IS NOT NULL AND h3_hex_ids != '{}'::jsonb)",
algorithm: :concurrently, if_not_exists: true
# end
end
end

View file

@ -16,7 +16,7 @@ class CreatePointsRawDataArchives < ActiveRecord::Migration[8.0]
end
add_index :points_raw_data_archives, :user_id
add_index :points_raw_data_archives, %i[user_id year month]
add_index :points_raw_data_archives, [:user_id, :year, :month]
add_index :points_raw_data_archives, :archived_at
add_foreign_key :points_raw_data_archives, :users, validate: false
end

View file

@ -27,19 +27,15 @@ class AddCompositeIndexToStats < ActiveRecord::Migration[8.0]
deleted_count = 0
loop do
batch_deleted = execute(<<-SQL.squish).cmd_tuples
DELETE FROM stats
WHERE id IN (
SELECT s1.id
FROM stats s1
WHERE EXISTS (
SELECT 1 FROM stats s2
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}
DELETE FROM stats s1
WHERE EXISTS (
SELECT 1 FROM stats s2
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}
SQL
break if batch_deleted.zero?

View file

@ -1,5 +1,3 @@
# frozen_string_literal: true
class AddVerifiedAtToPointsRawDataArchives < ActiveRecord::Migration[8.0]
def change
add_column :points_raw_data_archives, :verified_at, :datetime

View file

@ -4,10 +4,10 @@ class ChangeDigestsDistanceToBigint < ActiveRecord::Migration[8.0]
disable_ddl_transaction!
def up
change_column :digests, :distance, :bigint, null: false, default: 0
safety_assured { change_column :digests, :distance, :bigint, null: false, default: 0 }
end
def down
change_column :digests, :distance, :integer, null: false, default: 0
safety_assured { change_column :digests, :distance, :integer, null: false, default: 0 }
end
end

View file

@ -0,0 +1,50 @@
# 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