diff --git a/.app_version b/.app_version index 35aa2f3c..16c6b58f 100644 --- a/.app_version +++ b/.app_version @@ -1 +1 @@ -0.25.4 +0.25.5 diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 597df91b..d697d8f8 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -32,7 +32,6 @@ services: PROMETHEUS_EXPORTER_ENABLED: false PROMETHEUS_EXPORTER_HOST: 0.0.0.0 PROMETHEUS_EXPORTER_PORT: 9394 - ENABLE_TELEMETRY: false # More on telemetry: https://dawarich.app/docs/tutorials/telemetry dawarich_redis: image: redis:7.4-alpine container_name: dawarich_redis diff --git a/.gitignore b/.gitignore index b3a85915..3e2a87f6 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,8 @@ .dotnet/ .cursorrules .cursormemory.md + +/config/credentials/production.key +/config/credentials/production.yml.enc + +Makefile diff --git a/CHANGELOG.md b/CHANGELOG.md index 2da4e84c..095e92e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,37 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +# 0.25.5 - 2025-04-18 + +This release introduces a new way to send transactional emails using SMTP. Example may include password reset, email confirmation, etc. + +To enable SMTP mailing, you need to set the following environment variables: + +- `SMTP_SERVER` - SMTP server address. +- `SMTP_PORT` - SMTP server port. +- `SMTP_DOMAIN` - SMTP server domain. +- `SMTP_USERNAME` - SMTP server username. +- `SMTP_PASSWORD` - SMTP server password. +- `SMTP_FROM` - Email address to send emails from. + +This is optional feature and is not required for the app to work. + +## Removed + +- Optional telemetry was removed from the app. +- Sidekiq Web UI is now protected by basic auth in non-self-hosted mode. + +## Changed + +- `rake points:migrate_to_lonlat` task now also tries to extract latitude and longitude from `raw_data` column before using `longitude` and `latitude` columns to fill `lonlat` column. +- Docker entrypoints are now using `DATABASE_NAME` environment variable to check if Postgres is existing/available. + +## Added + +- You can now provide SMTP settings in ENV vars to send emails. +- You can now edit imports. #1044 #623 + + # 0.25.4 - 2025-04-02 ⚠️ This release includes a breaking change. ⚠️ diff --git a/Gemfile b/Gemfile index ec872bfe..1b6b8ece 100644 --- a/Gemfile +++ b/Gemfile @@ -27,6 +27,7 @@ gem 'activerecord-postgis-adapter' gem 'puma' gem 'pundit' gem 'rails', '~> 8.0' +gem 'rexml' gem 'rgeo' gem 'rgeo-activerecord' gem 'rswag-api' diff --git a/Gemfile.lock b/Gemfile.lock index ff5a3ab7..9b682dbc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -77,7 +77,7 @@ GEM uri (>= 0.13.1) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) - ast (2.4.2) + ast (2.4.3) attr_extras (7.1.0) aws-eventstream (1.3.2) aws-partitions (1.1072.0) @@ -105,7 +105,7 @@ GEM racc builder (3.3.0) byebug (11.1.3) - chartkick (5.1.3) + chartkick (5.1.4) coderay (1.1.3) concurrent-ruby (1.3.5) connection_pool (2.5.0) @@ -180,7 +180,7 @@ GEM rdoc (>= 4.0.0) reline (>= 0.4.2) jmespath (1.6.2) - json (2.10.1) + json (2.10.2) json-schema (5.0.1) addressable (~> 2.8) jwt (2.10.1) @@ -199,7 +199,7 @@ GEM kaminari-core (1.2.2) language_server-protocol (3.17.0.4) lint_roller (1.1.0) - logger (1.6.6) + logger (1.7.0) lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) @@ -217,7 +217,7 @@ GEM method_source (1.1.0) mini_mime (1.1.5) mini_portile2 (2.8.8) - minitest (5.25.4) + minitest (5.25.5) msgpack (1.7.3) multi_xml (0.7.1) bigdecimal (~> 3.1) @@ -251,7 +251,7 @@ GEM orm_adapter (0.5.0) ostruct (0.6.1) parallel (1.26.3) - parser (3.3.7.1) + parser (3.3.7.4) ast (~> 2.4.1) racc patience_diff (1.2.0) @@ -260,6 +260,7 @@ GEM pp (0.6.2) prettyprint prettyprint (0.2.0) + prism (1.4.0) prometheus_exporter (2.2.0) webrick pry (0.14.2) @@ -276,11 +277,11 @@ GEM public_suffix (6.0.1) puma (6.6.0) nio4r (~> 2.0) - pundit (2.4.0) + pundit (2.5.0) activesupport (>= 3.0.0) raabro (1.4.0) racc (1.8.1) - rack (3.1.10) + rack (3.1.12) rack-session (2.1.0) base64 (>= 0.1.0) rack (>= 3.0.0) @@ -321,9 +322,9 @@ GEM rake (13.2.1) rdoc (6.12.0) psych (>= 4.0.0) - redis (5.3.0) + redis (5.4.0) redis-client (>= 0.22.0) - redis-client (0.23.2) + redis-client (0.24.0) connection_pool regexp_parser (2.10.0) reline (0.6.0) @@ -366,7 +367,7 @@ GEM rswag-ui (2.16.0) actionpack (>= 5.2, < 8.1) railties (>= 5.2, < 8.1) - rubocop (1.72.1) + rubocop (1.75.2) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -374,16 +375,17 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.38.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.38.0) - parser (>= 3.3.1.0) - rubocop-rails (2.30.1) + rubocop-ast (1.44.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-rails (2.31.0) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) - rubocop (>= 1.72.1, < 2.0) + rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.38.0, < 2.0) ruby-progressbar (1.13.0) securerandom (0.4.1) @@ -450,7 +452,7 @@ GEM unicode-display_width (3.1.4) unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) - uri (1.0.2) + uri (1.0.3) useragent (0.16.11) warden (1.2.9) rack (>= 2.0.9) @@ -506,6 +508,7 @@ DEPENDENCIES pundit rails (~> 8.0) redis + rexml rgeo rgeo-activerecord rspec-rails diff --git a/Makefile b/Makefile deleted file mode 100644 index b4c221d0..00000000 --- a/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -build_and_push: - git tag -a "$(version)" -f -m "$(version)" - docker build . -t dawarich:$(version) --platform=linux/amd64 - docker tag dawarich:$(version) registry.chibi.rodeo/dawarich:$(version) - docker tag registry.chibi.rodeo/dawarich:$(version) registry.chibi.rodeo/dawarich:latest - docker push registry.chibi.rodeo/dawarich:$(version) diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index d409087c..f77c7a54 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -2,6 +2,8 @@ class HomeController < ApplicationController def index + # redirect_to 'https://dawarich.app', allow_other_host: true and return unless SELF_HOSTED + redirect_to map_url if current_user @points = current_user.tracked_points.without_raw_data if current_user diff --git a/app/controllers/imports_controller.rb b/app/controllers/imports_controller.rb index caf874a9..f93ba11a 100644 --- a/app/controllers/imports_controller.rb +++ b/app/controllers/imports_controller.rb @@ -5,7 +5,7 @@ class ImportsController < ApplicationController before_action :authenticate_user! before_action :authenticate_active_user!, only: %i[new create] - before_action :set_import, only: %i[show destroy] + before_action :set_import, only: %i[show edit update destroy] def index @imports = @@ -18,10 +18,18 @@ class ImportsController < ApplicationController def show; end + def edit; end + def new @import = Import.new end + def update + @import.update(import_params) + + redirect_to imports_url, notice: 'Import was successfully updated.', status: :see_other + end + def create files = import_params[:files].reject(&:blank?) diff --git a/app/jobs/telemetry_sending_job.rb b/app/jobs/telemetry_sending_job.rb deleted file mode 100644 index 7bec3b00..00000000 --- a/app/jobs/telemetry_sending_job.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -class TelemetrySendingJob < ApplicationJob - queue_as :default - - def perform - return unless ENV['ENABLE_TELEMETRY'] == 'true' - - data = Telemetry::Gather.new.call - Rails.logger.info("Telemetry data: #{data}") - - Telemetry::Send.new(data).call - end -end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 3c34c814..071c3510 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,4 +1,6 @@ +# frozen_string_literal: true + class ApplicationMailer < ActionMailer::Base - default from: "from@example.com" - layout "mailer" + default from: ENV['SMTP_FROM'] + layout 'mailer' end diff --git a/app/models/user.rb b/app/models/user.rb index dc0bb532..8d7282b4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -101,7 +101,7 @@ class User < ApplicationRecord end def can_subscribe? - active_until&.past? && !DawarichSettings.self_hosted? + (active_until.nil? || active_until&.past?) && !DawarichSettings.self_hosted? end def generate_subscription_token diff --git a/app/services/gpx/track_importer.rb b/app/services/gpx/track_importer.rb index 9abd1a56..24dd2798 100644 --- a/app/services/gpx/track_importer.rb +++ b/app/services/gpx/track_importer.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'rexml/document' + class Gpx::TrackImporter include Imports::Broadcaster diff --git a/app/services/photos/import_parser.rb b/app/services/photos/import_parser.rb index b91a9ca3..fec7bba8 100644 --- a/app/services/photos/import_parser.rb +++ b/app/services/photos/import_parser.rb @@ -3,16 +3,19 @@ class Photos::ImportParser include Imports::Broadcaster include PointValidation - attr_reader :import, :json, :user_id + attr_reader :import, :user_id def initialize(import, user_id) @import = import - @json = import.raw_data @user_id = user_id end def call - json.each.with_index(1) { |point, index| create_point(point, index) } + import.file.download do |file| + json = Oj.load(file) + + json.each.with_index(1) { |point, index| create_point(point, index) } + end end def create_point(point, index) diff --git a/app/services/points/raw_data_lonlat_extractor.rb b/app/services/points/raw_data_lonlat_extractor.rb new file mode 100644 index 00000000..b14b086a --- /dev/null +++ b/app/services/points/raw_data_lonlat_extractor.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +class Points::RawDataLonlatExtractor + def initialize(point) + @point = point + end + + def call + lonlat = extract_lonlat(@point) + + @point.update( + longitude: lonlat[0], + latitude: lonlat[1] + ) + end + + private + + # rubocop:disable Metrics/MethodLength + def extract_lonlat(point) + if point.raw_data.dig('activitySegment', 'waypointPath', 'waypoints', 0) + # google_semantic_history_parser + [ + point.raw_data['activitySegment']['waypointPath']['waypoints'][0]['lngE7'].to_f / 10**7, + point.raw_data['activitySegment']['waypointPath']['waypoints'][0]['latE7'].to_f / 10**7 + ] + elsif point.raw_data['longitudeE7'] && point.raw_data['latitudeE7'] + # google records + [ + point.raw_data['longitudeE7'].to_f / 10**7, + point.raw_data['latitudeE7'].to_f / 10**7 + ] + elsif point.raw_data.dig('position', 'LatLng') + # google phone export + raw_coordinates = point.raw_data['position']['LatLng'] + if raw_coordinates.include?('°') + raw_coordinates.split(', ').map { _1.chomp('°') } + else + raw_coordinates.delete('geo:').split(',') + end + elsif point.raw_data['lon'] && point.raw_data['lat'] + # gpx_track_importer, owntracks + [point.raw_data['lon'], point.raw_data['lat']] + elsif point.raw_data.dig('geometry', 'coordinates', 0) && point.raw_data.dig('geometry', 'coordinates', 1) + # geojson + [ + point.raw_data['geometry']['coordinates'][0], + point.raw_data['geometry']['coordinates'][1] + ] + elsif point.raw_data['longitude'] && point.raw_data['latitude'] + # immich_api, photoprism_api + [point.raw_data['longitude'], point.raw_data['latitude']] + end + end + # rubocop:enable Metrics/MethodLength +end diff --git a/app/services/telemetry/gather.rb b/app/services/telemetry/gather.rb deleted file mode 100644 index 90b7ee01..00000000 --- a/app/services/telemetry/gather.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -class Telemetry::Gather - def initialize(measurement: 'dawarich_usage_metrics') - @measurement = measurement - end - - def call - { - measurement:, - timestamp: Time.current.to_i, - tags: { instance_id: }, - fields: { dau:, app_version: } - } - end - - private - - attr_reader :measurement - - def instance_id - @instance_id ||= Digest::SHA2.hexdigest(User.first.api_key) - end - - def app_version - "\"#{APP_VERSION}\"" - end - - def dau - User.where(last_sign_in_at: Time.zone.today.beginning_of_day..Time.zone.today.end_of_day).count - end -end diff --git a/app/services/telemetry/send.rb b/app/services/telemetry/send.rb deleted file mode 100644 index 96f222af..00000000 --- a/app/services/telemetry/send.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -class Telemetry::Send - BUCKET = 'dawarich_metrics' - ORG = 'monitoring' - - def initialize(payload) - @payload = payload - end - - def call - return unless ENV['ENABLE_TELEMETRY'] == 'true' - - line_protocol = build_line_protocol - response = send_request(line_protocol) - handle_response(response) - end - - private - - attr_reader :payload - - def build_line_protocol - tag_string = payload[:tags].map { |k, v| "#{k}=#{v}" }.join(',') - field_string = payload[:fields].map { |k, v| "#{k}=#{v}" }.join(',') - - "#{payload[:measurement]},#{tag_string} #{field_string} #{payload[:timestamp].to_i}" - end - - def send_request(line_protocol) - HTTParty.post( - "#{TELEMETRY_URL}?org=#{ORG}&bucket=#{BUCKET}&precision=s", - body: line_protocol, - headers: { - 'Authorization' => "Token #{Base64.decode64(TELEMETRY_STRING)}", - 'Content-Type' => 'text/plain' - } - ) - end - - def handle_response(response) - Rails.logger.error("InfluxDB write failed: #{response.body}") unless response.success? - - response - end -end diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb index 417b5fa0..c968119a 100644 --- a/app/views/devise/shared/_links.html.erb +++ b/app/views/devise/shared/_links.html.erb @@ -5,6 +5,12 @@ <% end %> + <% if !SELF_HOSTED && defined?(devise_mapping) && devise_mapping&.registerable? && controller_name != 'registrations' %> +
+ <%= link_to "Register", new_registration_path(resource_name) %> +
+ <% end %> + <% if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<%= link_to "Forgot your password?", new_password_path(resource_name) %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 0763304b..50a43317 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -7,7 +7,6 @@ <%= csrf_meta_tags %> <%= csp_meta_tag %> - @@ -25,7 +24,7 @@
<%= yield %>
- <%= render 'shared/footer' %> + <%= render SELF_HOSTED ? 'shared/footer' : 'shared/legal_footer' %>
diff --git a/app/views/settings/subscriptions/index.html.erb b/app/views/settings/subscriptions/index.html.erb index 093b58a9..9342fcde 100644 --- a/app/views/settings/subscriptions/index.html.erb +++ b/app/views/settings/subscriptions/index.html.erb @@ -7,7 +7,7 @@

Hello there!

- <% if current_user.active_until.future? %> + <% if current_user.active_until&.future? %>

You are currently subscribed to Dawarich, hurray!

@@ -16,13 +16,13 @@ Your subscription will be valid for the next <%= days_left(current_user.active_until) %>.

- <%= link_to 'Manage subscription', "#{ENV['SUBSCRIPTION_URL']}/auth/dawarich?token=#{current_user.generate_subscription_token}", class: 'btn btn-primary my-4' %> + <%= link_to 'Manage subscription', "#{MANAGER_URL}/auth/dawarich?token=#{current_user.generate_subscription_token}", class: 'btn btn-primary my-4' %> <% else %>

You are currently not subscribed to Dawarich. How about we fix that?

- <%= link_to 'Manage subscription', "#{ENV['SUBSCRIPTION_URL']}/auth/dawarich?token=#{current_user.generate_subscription_token}", class: 'btn btn-primary my-4' %> + <%= link_to 'Manage subscription', "#{MANAGER_URL}/auth/dawarich?token=#{current_user.generate_subscription_token}", class: 'btn btn-primary my-4' %> <% end %>
diff --git a/app/views/shared/_footer.html.erb b/app/views/shared/_footer.html.erb index 944a96ae..a069be2d 100644 --- a/app/views/shared/_footer.html.erb +++ b/app/views/shared/_footer.html.erb @@ -1,4 +1,4 @@ -