mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-14 03:01:39 -05:00
Merge branch 'Freika:dev' into dev
This commit is contained in:
commit
7bea02dc95
39 changed files with 378 additions and 275 deletions
|
|
@ -1 +1 @@
|
||||||
0.25.4
|
0.25.5
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ services:
|
||||||
PROMETHEUS_EXPORTER_ENABLED: false
|
PROMETHEUS_EXPORTER_ENABLED: false
|
||||||
PROMETHEUS_EXPORTER_HOST: 0.0.0.0
|
PROMETHEUS_EXPORTER_HOST: 0.0.0.0
|
||||||
PROMETHEUS_EXPORTER_PORT: 9394
|
PROMETHEUS_EXPORTER_PORT: 9394
|
||||||
ENABLE_TELEMETRY: false # More on telemetry: https://dawarich.app/docs/tutorials/telemetry
|
|
||||||
dawarich_redis:
|
dawarich_redis:
|
||||||
image: redis:7.4-alpine
|
image: redis:7.4-alpine
|
||||||
container_name: dawarich_redis
|
container_name: dawarich_redis
|
||||||
|
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -65,3 +65,8 @@
|
||||||
.dotnet/
|
.dotnet/
|
||||||
.cursorrules
|
.cursorrules
|
||||||
.cursormemory.md
|
.cursormemory.md
|
||||||
|
|
||||||
|
/config/credentials/production.key
|
||||||
|
/config/credentials/production.yml.enc
|
||||||
|
|
||||||
|
Makefile
|
||||||
|
|
|
||||||
44
CHANGELOG.md
44
CHANGELOG.md
|
|
@ -5,11 +5,46 @@ 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.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. The `ENABLE_TELEMETRY` env var can be safely removed from docker compose.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
- Sidekiq web UI is now protected by basic auth. Use `SIDEKIQ_USERNAME` and `SIDEKIQ_PASSWORD` environment variables to set the credentials.
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- You can now provide SMTP settings in ENV vars to send emails.
|
||||||
|
- You can now edit imports. #1044 #623
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- Importing data from Immich now works correctly. #1019
|
||||||
|
|
||||||
|
|
||||||
# 0.25.4 - 2025-04-02
|
# 0.25.4 - 2025-04-02
|
||||||
|
|
||||||
⚠️ This release includes a breaking change. ⚠️
|
⚠️ This release includes a breaking change. ⚠️
|
||||||
|
|
||||||
Make sure to add `dawarich_storage` volume to your `docker-compose.yml` file. Example:
|
Make sure to add `dawarich_storage` volume and `SELF_HOSTED: "true"` to your `docker-compose.yml` file. Example:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
...
|
...
|
||||||
|
|
@ -21,6 +56,9 @@ Make sure to add `dawarich_storage` volume to your `docker-compose.yml` file. Ex
|
||||||
- dawarich_public:/var/app/public
|
- dawarich_public:/var/app/public
|
||||||
- dawarich_watched:/var/app/tmp/imports/watched
|
- dawarich_watched:/var/app/tmp/imports/watched
|
||||||
+ - dawarich_storage:/var/app/storage
|
+ - dawarich_storage:/var/app/storage
|
||||||
|
...
|
||||||
|
environment:
|
||||||
|
+ SELF_HOSTED: "true"
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
@ -31,6 +69,10 @@ Make sure to add `dawarich_storage` volume to your `docker-compose.yml` file. Ex
|
||||||
- dawarich_public:/var/app/public
|
- dawarich_public:/var/app/public
|
||||||
- dawarich_watched:/var/app/tmp/imports/watched
|
- dawarich_watched:/var/app/tmp/imports/watched
|
||||||
+ - dawarich_storage:/var/app/storage
|
+ - dawarich_storage:/var/app/storage
|
||||||
|
...
|
||||||
|
environment:
|
||||||
|
+ SELF_HOSTED: "true"
|
||||||
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
dawarich_db_data:
|
dawarich_db_data:
|
||||||
|
|
|
||||||
1
Gemfile
1
Gemfile
|
|
@ -27,6 +27,7 @@ gem 'activerecord-postgis-adapter'
|
||||||
gem 'puma'
|
gem 'puma'
|
||||||
gem 'pundit'
|
gem 'pundit'
|
||||||
gem 'rails', '~> 8.0'
|
gem 'rails', '~> 8.0'
|
||||||
|
gem 'rexml'
|
||||||
gem 'rgeo'
|
gem 'rgeo'
|
||||||
gem 'rgeo-activerecord'
|
gem 'rgeo-activerecord'
|
||||||
gem 'rswag-api'
|
gem 'rswag-api'
|
||||||
|
|
|
||||||
47
Gemfile.lock
47
Gemfile.lock
|
|
@ -77,7 +77,7 @@ GEM
|
||||||
uri (>= 0.13.1)
|
uri (>= 0.13.1)
|
||||||
addressable (2.8.7)
|
addressable (2.8.7)
|
||||||
public_suffix (>= 2.0.2, < 7.0)
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
ast (2.4.2)
|
ast (2.4.3)
|
||||||
attr_extras (7.1.0)
|
attr_extras (7.1.0)
|
||||||
aws-eventstream (1.3.2)
|
aws-eventstream (1.3.2)
|
||||||
aws-partitions (1.1072.0)
|
aws-partitions (1.1072.0)
|
||||||
|
|
@ -104,8 +104,8 @@ GEM
|
||||||
brakeman (7.0.2)
|
brakeman (7.0.2)
|
||||||
racc
|
racc
|
||||||
builder (3.3.0)
|
builder (3.3.0)
|
||||||
byebug (11.1.3)
|
byebug (12.0.0)
|
||||||
chartkick (5.1.3)
|
chartkick (5.1.4)
|
||||||
coderay (1.1.3)
|
coderay (1.1.3)
|
||||||
concurrent-ruby (1.3.5)
|
concurrent-ruby (1.3.5)
|
||||||
connection_pool (2.5.0)
|
connection_pool (2.5.0)
|
||||||
|
|
@ -180,7 +180,7 @@ GEM
|
||||||
rdoc (>= 4.0.0)
|
rdoc (>= 4.0.0)
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.10.1)
|
json (2.10.2)
|
||||||
json-schema (5.0.1)
|
json-schema (5.0.1)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
jwt (2.10.1)
|
jwt (2.10.1)
|
||||||
|
|
@ -199,7 +199,7 @@ GEM
|
||||||
kaminari-core (1.2.2)
|
kaminari-core (1.2.2)
|
||||||
language_server-protocol (3.17.0.4)
|
language_server-protocol (3.17.0.4)
|
||||||
lint_roller (1.1.0)
|
lint_roller (1.1.0)
|
||||||
logger (1.6.6)
|
logger (1.7.0)
|
||||||
lograge (0.14.0)
|
lograge (0.14.0)
|
||||||
actionpack (>= 4)
|
actionpack (>= 4)
|
||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
|
|
@ -217,7 +217,7 @@ GEM
|
||||||
method_source (1.1.0)
|
method_source (1.1.0)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.8)
|
mini_portile2 (2.8.8)
|
||||||
minitest (5.25.4)
|
minitest (5.25.5)
|
||||||
msgpack (1.7.3)
|
msgpack (1.7.3)
|
||||||
multi_xml (0.7.1)
|
multi_xml (0.7.1)
|
||||||
bigdecimal (~> 3.1)
|
bigdecimal (~> 3.1)
|
||||||
|
|
@ -251,7 +251,7 @@ GEM
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
ostruct (0.6.1)
|
ostruct (0.6.1)
|
||||||
parallel (1.26.3)
|
parallel (1.26.3)
|
||||||
parser (3.3.7.1)
|
parser (3.3.7.4)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
patience_diff (1.2.0)
|
patience_diff (1.2.0)
|
||||||
|
|
@ -260,14 +260,15 @@ GEM
|
||||||
pp (0.6.2)
|
pp (0.6.2)
|
||||||
prettyprint
|
prettyprint
|
||||||
prettyprint (0.2.0)
|
prettyprint (0.2.0)
|
||||||
|
prism (1.4.0)
|
||||||
prometheus_exporter (2.2.0)
|
prometheus_exporter (2.2.0)
|
||||||
webrick
|
webrick
|
||||||
pry (0.14.2)
|
pry (0.15.2)
|
||||||
coderay (~> 1.1)
|
coderay (~> 1.1)
|
||||||
method_source (~> 1.0)
|
method_source (~> 1.0)
|
||||||
pry-byebug (3.10.1)
|
pry-byebug (3.11.0)
|
||||||
byebug (~> 11.0)
|
byebug (~> 12.0)
|
||||||
pry (>= 0.13, < 0.15)
|
pry (>= 0.13, < 0.16)
|
||||||
pry-rails (0.3.11)
|
pry-rails (0.3.11)
|
||||||
pry (>= 0.13.0)
|
pry (>= 0.13.0)
|
||||||
psych (5.2.3)
|
psych (5.2.3)
|
||||||
|
|
@ -276,11 +277,11 @@ GEM
|
||||||
public_suffix (6.0.1)
|
public_suffix (6.0.1)
|
||||||
puma (6.6.0)
|
puma (6.6.0)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
pundit (2.4.0)
|
pundit (2.5.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (3.1.10)
|
rack (3.1.12)
|
||||||
rack-session (2.1.0)
|
rack-session (2.1.0)
|
||||||
base64 (>= 0.1.0)
|
base64 (>= 0.1.0)
|
||||||
rack (>= 3.0.0)
|
rack (>= 3.0.0)
|
||||||
|
|
@ -321,9 +322,9 @@ GEM
|
||||||
rake (13.2.1)
|
rake (13.2.1)
|
||||||
rdoc (6.12.0)
|
rdoc (6.12.0)
|
||||||
psych (>= 4.0.0)
|
psych (>= 4.0.0)
|
||||||
redis (5.3.0)
|
redis (5.4.0)
|
||||||
redis-client (>= 0.22.0)
|
redis-client (>= 0.22.0)
|
||||||
redis-client (0.23.2)
|
redis-client (0.24.0)
|
||||||
connection_pool
|
connection_pool
|
||||||
regexp_parser (2.10.0)
|
regexp_parser (2.10.0)
|
||||||
reline (0.6.0)
|
reline (0.6.0)
|
||||||
|
|
@ -366,7 +367,7 @@ GEM
|
||||||
rswag-ui (2.16.0)
|
rswag-ui (2.16.0)
|
||||||
actionpack (>= 5.2, < 8.1)
|
actionpack (>= 5.2, < 8.1)
|
||||||
railties (>= 5.2, < 8.1)
|
railties (>= 5.2, < 8.1)
|
||||||
rubocop (1.72.1)
|
rubocop (1.75.2)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (~> 3.17.0.2)
|
language_server-protocol (~> 3.17.0.2)
|
||||||
lint_roller (~> 1.1.0)
|
lint_roller (~> 1.1.0)
|
||||||
|
|
@ -374,16 +375,17 @@ GEM
|
||||||
parser (>= 3.3.0.2)
|
parser (>= 3.3.0.2)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 2.9.3, < 3.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)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 4.0)
|
unicode-display_width (>= 2.4.0, < 4.0)
|
||||||
rubocop-ast (1.38.0)
|
rubocop-ast (1.44.0)
|
||||||
parser (>= 3.3.1.0)
|
parser (>= 3.3.7.2)
|
||||||
rubocop-rails (2.30.1)
|
prism (~> 1.4)
|
||||||
|
rubocop-rails (2.31.0)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
lint_roller (~> 1.1)
|
lint_roller (~> 1.1)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 1.72.1, < 2.0)
|
rubocop (>= 1.75.0, < 2.0)
|
||||||
rubocop-ast (>= 1.38.0, < 2.0)
|
rubocop-ast (>= 1.38.0, < 2.0)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
securerandom (0.4.1)
|
securerandom (0.4.1)
|
||||||
|
|
@ -450,7 +452,7 @@ GEM
|
||||||
unicode-display_width (3.1.4)
|
unicode-display_width (3.1.4)
|
||||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||||
unicode-emoji (4.0.4)
|
unicode-emoji (4.0.4)
|
||||||
uri (1.0.2)
|
uri (1.0.3)
|
||||||
useragent (0.16.11)
|
useragent (0.16.11)
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
|
|
@ -506,6 +508,7 @@ DEPENDENCIES
|
||||||
pundit
|
pundit
|
||||||
rails (~> 8.0)
|
rails (~> 8.0)
|
||||||
redis
|
redis
|
||||||
|
rexml
|
||||||
rgeo
|
rgeo
|
||||||
rgeo-activerecord
|
rgeo-activerecord
|
||||||
rspec-rails
|
rspec-rails
|
||||||
|
|
|
||||||
6
Makefile
6
Makefile
|
|
@ -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)
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
class HomeController < ApplicationController
|
class HomeController < ApplicationController
|
||||||
def index
|
def index
|
||||||
|
# redirect_to 'https://dawarich.app', allow_other_host: true and return unless SELF_HOSTED
|
||||||
|
|
||||||
redirect_to map_url if current_user
|
redirect_to map_url if current_user
|
||||||
|
|
||||||
@points = current_user.tracked_points.without_raw_data if current_user
|
@points = current_user.tracked_points.without_raw_data if current_user
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ class ImportsController < ApplicationController
|
||||||
|
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
before_action :authenticate_active_user!, only: %i[new create]
|
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
|
def index
|
||||||
@imports =
|
@imports =
|
||||||
|
|
@ -18,10 +18,18 @@ class ImportsController < ApplicationController
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
|
def edit; end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@import = Import.new
|
@import = Import.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@import.update(import_params)
|
||||||
|
|
||||||
|
redirect_to imports_url, notice: 'Import was successfully updated.', status: :see_other
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
files = import_params[:files].reject(&:blank?)
|
files = import_params[:files].reject(&:blank?)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ApplicationMailer < ActionMailer::Base
|
class ApplicationMailer < ActionMailer::Base
|
||||||
default from: "from@example.com"
|
default from: ENV['SMTP_FROM']
|
||||||
layout "mailer"
|
layout 'mailer'
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_subscribe?
|
def can_subscribe?
|
||||||
active_until&.past? && !DawarichSettings.self_hosted?
|
(active_until.nil? || active_until&.past?) && !DawarichSettings.self_hosted?
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_subscription_token
|
def generate_subscription_token
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rexml/document'
|
||||||
|
|
||||||
class Gpx::TrackImporter
|
class Gpx::TrackImporter
|
||||||
include Imports::Broadcaster
|
include Imports::Broadcaster
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,19 @@
|
||||||
class Photos::ImportParser
|
class Photos::ImportParser
|
||||||
include Imports::Broadcaster
|
include Imports::Broadcaster
|
||||||
include PointValidation
|
include PointValidation
|
||||||
attr_reader :import, :json, :user_id
|
attr_reader :import, :user_id
|
||||||
|
|
||||||
def initialize(import, user_id)
|
def initialize(import, user_id)
|
||||||
@import = import
|
@import = import
|
||||||
@json = import.raw_data
|
|
||||||
@user_id = user_id
|
@user_id = user_id
|
||||||
end
|
end
|
||||||
|
|
||||||
def call
|
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
|
end
|
||||||
|
|
||||||
def create_point(point, index)
|
def create_point(point, index)
|
||||||
|
|
|
||||||
56
app/services/points/raw_data_lonlat_extractor.rb
Normal file
56
app/services/points/raw_data_lonlat_extractor.rb
Normal file
|
|
@ -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
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -5,6 +5,12 @@
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<% if !SELF_HOSTED && defined?(devise_mapping) && devise_mapping&.registerable? && controller_name != 'registrations' %>
|
||||||
|
<div class='my-2'>
|
||||||
|
<%= link_to "Register", new_registration_path(resource_name) %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
|
<% if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
|
||||||
<div class='my-2'>
|
<div class='my-2'>
|
||||||
<%= link_to "Forgot your password?", new_password_path(resource_name) %>
|
<%= link_to "Forgot your password?", new_password_path(resource_name) %>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
<%= csrf_meta_tags %>
|
<%= csrf_meta_tags %>
|
||||||
<%= csp_meta_tag %>
|
<%= csp_meta_tag %>
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@4.12.14/dist/full.css" rel="stylesheet" type="text/css">
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"/>
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"/>
|
||||||
|
|
||||||
|
|
@ -25,7 +24,7 @@
|
||||||
<div class="flex flex-row gap-5 w-full px-5">
|
<div class="flex flex-row gap-5 w-full px-5">
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
</div>
|
</div>
|
||||||
<%= render 'shared/footer' %>
|
<%= render SELF_HOSTED ? 'shared/footer' : 'shared/legal_footer' %>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<div class="hero-content text-center">
|
<div class="hero-content text-center">
|
||||||
<div class="max-w-md">
|
<div class="max-w-md">
|
||||||
<h1 class="text-5xl font-bold">Hello there!</h1>
|
<h1 class="text-5xl font-bold">Hello there!</h1>
|
||||||
<% if current_user.active_until.future? %>
|
<% if current_user.active_until&.future? %>
|
||||||
<p class="py-6">
|
<p class="py-6">
|
||||||
You are currently subscribed to Dawarich, hurray!
|
You are currently subscribed to Dawarich, hurray!
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -16,13 +16,13 @@
|
||||||
Your subscription will be valid for the next <span class="text-accent"><%= days_left(current_user.active_until) %></span>.
|
Your subscription will be valid for the next <span class="text-accent"><%= days_left(current_user.active_until) %></span>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<%= 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 %>
|
<% else %>
|
||||||
<p class="py-6">
|
<p class="py-6">
|
||||||
You are currently not subscribed to Dawarich. How about we fix that?
|
You are currently not subscribed to Dawarich. How about we fix that?
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<%= 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<footer class="footer footer-center p-4 bg-base-300 text-base-content">
|
<footer class="footer bg-base-200 text-content-neutral p-4">
|
||||||
<aside>
|
<aside>
|
||||||
<p><a href="https://dawarich.app/" class="link hover:no-underline" target="_blank">Dawarich</a> 2023-<%=Time.zone.now.year %></p>
|
<p><a href="https://dawarich.app/" class="link hover:no-underline" target="_blank">Dawarich</a> 2023-<%=Time.zone.now.year %></p>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
|
||||||
34
app/views/shared/_legal_footer.html.erb
Normal file
34
app/views/shared/_legal_footer.html.erb
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<footer class="footer bg-base-200 text-content-neutral p-4">
|
||||||
|
<nav>
|
||||||
|
<h6 class="footer-title"><strong>Dawarich</strong></h6>
|
||||||
|
<p>
|
||||||
|
Made and hosted in 🇪🇺 Europe
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Copyright © <%= Time.zone.now.year %> ZeitFlow UG
|
||||||
|
</p>
|
||||||
|
</nav>
|
||||||
|
<nav>
|
||||||
|
<h6 class="footer-title"><strong>Community</strong></h6>
|
||||||
|
<a class="hover:underline" href="https://discord.gg/pHsBjpt5J8" target="_blank">Discord</a>
|
||||||
|
<a class="hover:underline" href="https://x.com/freymakesstuff" target="_blank">X</a>
|
||||||
|
<a class="hover:underline" href="https://github.com/Freika/dawarich" target="_blank">Github</a>
|
||||||
|
<a class="hover:underline" href="https://mastodon.social/@dawarich" target="_blank">Mastodon</a>
|
||||||
|
</nav>
|
||||||
|
<nav>
|
||||||
|
<h6 class="footer-title"><strong>Docs</strong></h6>
|
||||||
|
<a class="hover:underline" href="https://dawarich.app/docs/intro" target="_blank">Tutorial</a>
|
||||||
|
<a class="hover:underline" href="https://dawarich.app/docs/tutorials/import-existing-data" target="_blank">Import existing data</a>
|
||||||
|
<a class="hover:underline" href="https://dawarich.app/docs/tutorials/export-your-data" target="_blank">Exporting data</a>
|
||||||
|
<a class="hover:underline" href="https://dawarich.app/docs/FAQ" target="_blank">FAQ</a>
|
||||||
|
<a class="hover:underline" href="https://dawarich.app/contact" target="_blank">Contact</a>
|
||||||
|
</nav>
|
||||||
|
<nav>
|
||||||
|
<h6 class="footer-title"><strong>More</strong></h6>
|
||||||
|
<a class="hover:underline" href="https://dawarich.app/privacy-policy" target="_blank">Privacy policy</a>
|
||||||
|
<a class="hover:underline" href="https://dawarich.app/terms-and-conditions" target="_blank">Terms and Conditions</a>
|
||||||
|
<a class="hover:underline" href="https://dawarich.app/refund-policy" target="_blank">Refund policy</a>
|
||||||
|
<a class="hover:underline" href="https://dawarich.app/impressum" target="_blank">Impressum</a>
|
||||||
|
<a class="hover:underline" href="https://dawarich.app/blog" target="_blank">Blog</a>
|
||||||
|
</nav>
|
||||||
|
</footer>
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
</details>
|
</details>
|
||||||
</li>
|
</li>
|
||||||
<% if user_signed_in? && current_user.can_subscribe? %>
|
<% if user_signed_in? && current_user.can_subscribe? %>
|
||||||
<li><%= link_to 'Subscribe', "#{ENV['SUBSCRIPTION_URL']}/auth/dawarich?token=#{current_user.generate_subscription_token}", class: 'btn btn-sm btn-success' %></li>
|
<li><%= link_to 'Subscribe', "#{MANAGER_URL}/auth/dawarich?token=#{current_user.generate_subscription_token}", class: 'btn btn-sm btn-success' %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -71,7 +71,7 @@
|
||||||
<ul class="menu menu-horizontal bg-base-100 rounded-box px-1">
|
<ul class="menu menu-horizontal bg-base-100 rounded-box px-1">
|
||||||
<% if user_signed_in? %>
|
<% if user_signed_in? %>
|
||||||
<% if current_user.can_subscribe? %>
|
<% if current_user.can_subscribe? %>
|
||||||
<li><%= link_to 'Subscribe', "#{ENV['SUBSCRIPTION_URL']}/auth/dawarich?token=#{current_user.generate_subscription_token}", class: 'btn btn-sm btn-success' %></li>
|
<li><%= link_to 'Subscribe', "#{MANAGER_URL}/auth/dawarich?token=#{current_user.generate_subscription_token}", class: 'btn btn-sm btn-success' %></li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="dropdown dropdown-end dropdown-bottom dropdown-hover"
|
<div class="dropdown dropdown-end dropdown-bottom dropdown-hover"
|
||||||
|
|
@ -98,8 +98,8 @@
|
||||||
</div>
|
</div>
|
||||||
<ul tabindex="0" class="dropdown-content z-[5000] menu p-2 shadow-lg bg-base-100 rounded-box min-w-52" data-notifications-target="list">
|
<ul tabindex="0" class="dropdown-content z-[5000] menu p-2 shadow-lg bg-base-100 rounded-box min-w-52" data-notifications-target="list">
|
||||||
<li><%= link_to 'See all', notifications_path %></li>
|
<li><%= link_to 'See all', notifications_path %></li>
|
||||||
<div class="divider p-0 m-0"></div>
|
|
||||||
<% @unread_notifications.first(10).each do |notification| %>
|
<% @unread_notifications.first(10).each do |notification| %>
|
||||||
|
<div class="divider p-0 m-0"></div>
|
||||||
<li class='notification-item'>
|
<li class='notification-item'>
|
||||||
<%= link_to notification do %>
|
<%= link_to notification do %>
|
||||||
<%= notification.title %>
|
<%= notification.title %>
|
||||||
|
|
@ -126,6 +126,9 @@
|
||||||
</li>
|
</li>
|
||||||
<% else %>
|
<% else %>
|
||||||
<li><%= link_to 'Login', new_user_session_path %></li>
|
<li><%= link_to 'Login', new_user_session_path %></li>
|
||||||
|
<% if !SELF_HOSTED && defined?(devise_mapping) && devise_mapping&.registerable? %>
|
||||||
|
<li><%= link_to 'Register', new_user_registration_path %></li>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -107,4 +107,17 @@ Rails.application.configure do
|
||||||
|
|
||||||
config.action_mailer.default_url_options = { host: hosts.first, port: 3000 }
|
config.action_mailer.default_url_options = { host: hosts.first, port: 3000 }
|
||||||
config.hosts.concat(hosts) if hosts.present?
|
config.hosts.concat(hosts) if hosts.present?
|
||||||
|
|
||||||
|
config.action_mailer.delivery_method = :smtp
|
||||||
|
config.action_mailer.smtp_settings = {
|
||||||
|
address: ENV['SMTP_SERVER'],
|
||||||
|
port: ENV['SMTP_PORT'],
|
||||||
|
domain: ENV['SMTP_DOMAIN'],
|
||||||
|
user_name: ENV['SMTP_USERNAME'],
|
||||||
|
password: ENV['SMTP_PASSWORD'],
|
||||||
|
authentication: 'plain',
|
||||||
|
enable_starttls: true,
|
||||||
|
open_timeout: 5,
|
||||||
|
read_timeout: 5
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,6 @@ DISTANCE_UNIT = ENV.fetch('DISTANCE_UNIT', 'km').to_sym
|
||||||
|
|
||||||
APP_VERSION = File.read('.app_version').strip
|
APP_VERSION = File.read('.app_version').strip
|
||||||
|
|
||||||
TELEMETRY_STRING = Base64.encode64('IjVFvb8j3P9-ArqhSGav9j8YcJaQiuNIzkfOPKQDk2lvKXqb8t1NSRv50oBkaKtlrB_ZRzO9NdurpMtncV_HYQ==')
|
|
||||||
TELEMETRY_URL = 'https://influxdb2.frey.today/api/v2/write'
|
|
||||||
|
|
||||||
# Reverse geocoding settings
|
# Reverse geocoding settings
|
||||||
PHOTON_API_HOST = ENV.fetch('PHOTON_API_HOST', nil)
|
PHOTON_API_HOST = ENV.fetch('PHOTON_API_HOST', nil)
|
||||||
PHOTON_API_KEY = ENV.fetch('PHOTON_API_KEY', nil)
|
PHOTON_API_KEY = ENV.fetch('PHOTON_API_KEY', nil)
|
||||||
|
|
@ -23,3 +20,4 @@ GEOAPIFY_API_KEY = ENV.fetch('GEOAPIFY_API_KEY', nil)
|
||||||
# /Reverse geocoding settings
|
# /Reverse geocoding settings
|
||||||
|
|
||||||
SENTRY_DSN = ENV.fetch('SENTRY_DSN', nil)
|
SENTRY_DSN = ENV.fetch('SENTRY_DSN', nil)
|
||||||
|
MANAGER_URL = SELF_HOSTED ? nil : ENV.fetch('MANAGER_URL', nil)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ Devise.setup do |config|
|
||||||
# Configure the e-mail address which will be shown in Devise::Mailer,
|
# Configure the e-mail address which will be shown in Devise::Mailer,
|
||||||
# note that it will be overwritten if you use your own mailer class
|
# note that it will be overwritten if you use your own mailer class
|
||||||
# with default "from" parameter.
|
# with default "from" parameter.
|
||||||
config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
|
config.mailer_sender = ENV['SMTP_FROM']
|
||||||
|
|
||||||
# Configure the class responsible to send e-mails.
|
# Configure the class responsible to send e-mails.
|
||||||
# config.mailer = 'Devise::Mailer'
|
# config.mailer = 'Devise::Mailer'
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,22 @@ Rails.application.routes.draw do
|
||||||
mount ActionCable.server => '/cable'
|
mount ActionCable.server => '/cable'
|
||||||
mount Rswag::Api::Engine => '/api-docs'
|
mount Rswag::Api::Engine => '/api-docs'
|
||||||
mount Rswag::Ui::Engine => '/api-docs'
|
mount Rswag::Ui::Engine => '/api-docs'
|
||||||
authenticate :user, ->(u) { u.admin? && DawarichSettings.self_hosted? } do
|
|
||||||
|
Sidekiq::Web.use(Rack::Auth::Basic) do |username, password|
|
||||||
|
ActiveSupport::SecurityUtils.secure_compare(
|
||||||
|
::Digest::SHA256.hexdigest(username),
|
||||||
|
::Digest::SHA256.hexdigest(ENV['SIDEKIQ_USERNAME'])
|
||||||
|
) &
|
||||||
|
ActiveSupport::SecurityUtils.secure_compare(
|
||||||
|
::Digest::SHA256.hexdigest(password),
|
||||||
|
::Digest::SHA256.hexdigest(ENV['SIDEKIQ_PASSWORD'])
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
authenticate :user, lambda { |u|
|
||||||
|
(u.admin? && DawarichSettings.self_hosted?) ||
|
||||||
|
(u.admin? && ENV['SIDEKIQ_USERNAME'].present? && ENV['SIDEKIQ_PASSWORD'].present?)
|
||||||
|
} do
|
||||||
mount Sidekiq::Web => '/sidekiq'
|
mount Sidekiq::Web => '/sidekiq'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ class RunInitialVisitSuggestion < ActiveRecord::Migration[7.1]
|
||||||
start_at = 30.years.ago
|
start_at = 30.years.ago
|
||||||
end_at = Time.current
|
end_at = Time.current
|
||||||
|
|
||||||
VisitSuggestingJob.perform_later(start_at:, end_at:)
|
User.find_each do |user|
|
||||||
|
VisitSuggestingJob.perform_later(user_id: user.id, start_at:, end_at:)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def down
|
def down
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class CreateTelemetryNotification < ActiveRecord::Migration[7.2]
|
class CreateTelemetryNotification < ActiveRecord::Migration[7.2]
|
||||||
def up
|
def up; end
|
||||||
# TODO: Remove
|
|
||||||
# User.find_each do |user|
|
|
||||||
# Notifications::Create.new(
|
|
||||||
# user:, kind: :info, title: 'Telemetry enabled', content: notification_content
|
|
||||||
# ).call
|
|
||||||
# end
|
|
||||||
end
|
|
||||||
|
|
||||||
def down
|
def down; end
|
||||||
raise ActiveRecord::IrreversibleMigration
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def notification_content
|
|
||||||
<<~CONTENT
|
|
||||||
<p>With the release 0.19.2, Dawarich now can collect usage some metrics and send them to InfluxDB.</p>
|
|
||||||
<br>
|
|
||||||
<p>Before this release, the only metrics that could be somehow tracked by developers (only <a href="https://github.com/Freika" class="underline">Freika</a>, as of now) were the number of stars on GitHub and the overall number of docker images being pulled, across all versions of Dawarich, non-splittable by version. New in-app telemetry will allow us to track more granular metrics, allowing me to make decisions based on facts, not just guesses.</p>
|
|
||||||
<br>
|
|
||||||
<p>I'm aware about the privacy concerns, so I want to be very transparent about what data is being sent and how it's used.</p>
|
|
||||||
<br>
|
|
||||||
<p>Data being sent:</p>
|
|
||||||
<br>
|
|
||||||
<ul class="list-disc">
|
|
||||||
<li>Number of DAU (Daily Active Users)</li>
|
|
||||||
<li>App version</li>
|
|
||||||
<li>Instance ID (unique identifier of the Dawarich instance built by hashing the api key of the first user in the database)</li>
|
|
||||||
</ul>
|
|
||||||
<br>
|
|
||||||
<p>The data is being sent to a InfluxDB instance hosted by me and won't be shared with anyone.</p>
|
|
||||||
<br>
|
|
||||||
<p>Basically this set of metrics allows me to see how many people are using Dawarich and what versions they are using. No other data is being sent, nor it gives me any knowledge about individual users or their data or activity.</p>
|
|
||||||
<br>
|
|
||||||
<p>The telemetry is enabled by default, but it <strong class="text-info underline">can be disabled</strong> by setting <code>DISABLE_TELEMETRY</code> env var to <code>true</code>. The dataset might change in the future, but any changes will be documented here in the changelog and in every release as well as on the <a href="https://dawarich.app/docs/tutorials/telemetry" class="underline">telemetry page</a> of the website docs.</p>
|
|
||||||
<br>
|
|
||||||
<p>You can read more about it in the <a href="https://github.com/Freika/dawarich/releases/tag/0.19.2" class="underline">release page</a>.</p>
|
|
||||||
CONTENT
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ ENV BUNDLE_PATH=/usr/local/bundle/gems
|
||||||
ENV RAILS_LOG_TO_STDOUT=true
|
ENV RAILS_LOG_TO_STDOUT=true
|
||||||
ENV RAILS_PORT=3000
|
ENV RAILS_PORT=3000
|
||||||
ENV RAILS_ENV=development
|
ENV RAILS_ENV=development
|
||||||
|
ENV SELF_HOSTED=true
|
||||||
|
|
||||||
# Install dependencies for application
|
# Install dependencies for application
|
||||||
RUN apk -U add --no-cache \
|
RUN apk -U add --no-cache \
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,8 @@ services:
|
||||||
PROMETHEUS_EXPORTER_ENABLED: false
|
PROMETHEUS_EXPORTER_ENABLED: false
|
||||||
PROMETHEUS_EXPORTER_HOST: 0.0.0.0
|
PROMETHEUS_EXPORTER_HOST: 0.0.0.0
|
||||||
PROMETHEUS_EXPORTER_PORT: 9394
|
PROMETHEUS_EXPORTER_PORT: 9394
|
||||||
ENABLE_TELEMETRY: false # More on telemetry: https://dawarich.app/docs/tutorials/telemetry
|
|
||||||
SELF_HOSTED: "true"
|
SELF_HOSTED: "true"
|
||||||
|
RAILS_MASTER_KEY: ${RAILS_MASTER_KEY}
|
||||||
logging:
|
logging:
|
||||||
driver: "json-file"
|
driver: "json-file"
|
||||||
options:
|
options:
|
||||||
|
|
@ -122,8 +122,8 @@ services:
|
||||||
PROMETHEUS_EXPORTER_ENABLED: false
|
PROMETHEUS_EXPORTER_ENABLED: false
|
||||||
PROMETHEUS_EXPORTER_HOST: dawarich_app
|
PROMETHEUS_EXPORTER_HOST: dawarich_app
|
||||||
PROMETHEUS_EXPORTER_PORT: 9394
|
PROMETHEUS_EXPORTER_PORT: 9394
|
||||||
ENABLE_TELEMETRY: false # More on telemetry: https://dawarich.app/docs/tutorials/telemetry
|
|
||||||
SELF_HOSTED: "true"
|
SELF_HOSTED: "true"
|
||||||
|
RAILS_MASTER_KEY: ${RAILS_MASTER_KEY}
|
||||||
logging:
|
logging:
|
||||||
driver: "json-file"
|
driver: "json-file"
|
||||||
options:
|
options:
|
||||||
|
|
|
||||||
|
|
@ -14,17 +14,19 @@ if [ -n "$DATABASE_URL" ]; then
|
||||||
DATABASE_PORT=$(echo $DATABASE_URL | awk -F[@/:] '{print $5}')
|
DATABASE_PORT=$(echo $DATABASE_URL | awk -F[@/:] '{print $5}')
|
||||||
DATABASE_USERNAME=$(echo $DATABASE_URL | awk -F[:/@] '{print $4}')
|
DATABASE_USERNAME=$(echo $DATABASE_URL | awk -F[:/@] '{print $4}')
|
||||||
DATABASE_PASSWORD=$(echo $DATABASE_URL | awk -F[:/@] '{print $5}')
|
DATABASE_PASSWORD=$(echo $DATABASE_URL | awk -F[:/@] '{print $5}')
|
||||||
|
DATABASE_NAME=$(echo $DATABASE_URL | awk -F[@/] '{print $5}')
|
||||||
else
|
else
|
||||||
# Use existing environment variables
|
# Use existing environment variables
|
||||||
DATABASE_HOST=${DATABASE_HOST}
|
DATABASE_HOST=${DATABASE_HOST}
|
||||||
DATABASE_PORT=${DATABASE_PORT}
|
DATABASE_PORT=${DATABASE_PORT}
|
||||||
DATABASE_USERNAME=${DATABASE_USERNAME}
|
DATABASE_USERNAME=${DATABASE_USERNAME}
|
||||||
DATABASE_PASSWORD=${DATABASE_PASSWORD}
|
DATABASE_PASSWORD=${DATABASE_PASSWORD}
|
||||||
|
DATABASE_NAME=${DATABASE_NAME}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Wait for the database to become available
|
# Wait for the database to become available
|
||||||
echo "⏳ Waiting for database to be ready..."
|
echo "⏳ Waiting for database to be ready..."
|
||||||
until PGPASSWORD=$DATABASE_PASSWORD psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USERNAME" -c '\q'; do
|
until PGPASSWORD=$DATABASE_PASSWORD psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USERNAME" -d "$DATABASE_NAME" -c '\q'; do
|
||||||
>&2 echo "Postgres is unavailable - retrying..."
|
>&2 echo "Postgres is unavailable - retrying..."
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
|
||||||
|
|
@ -29,16 +29,16 @@ rm -f $APP_PATH/tmp/pids/server.pid
|
||||||
|
|
||||||
# Wait for the database to become available
|
# Wait for the database to become available
|
||||||
echo "⏳ Waiting for database to be ready..."
|
echo "⏳ Waiting for database to be ready..."
|
||||||
until PGPASSWORD=$DATABASE_PASSWORD psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USERNAME" -c '\q'; do
|
until PGPASSWORD=$DATABASE_PASSWORD psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USERNAME" -d "$DATABASE_NAME" -c '\q'; do
|
||||||
>&2 echo "Postgres is unavailable - retrying..."
|
>&2 echo "Postgres is unavailable - retrying..."
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
echo "✅ PostgreSQL is ready!"
|
echo "✅ PostgreSQL is ready!"
|
||||||
|
|
||||||
# Create database if it doesn't exist
|
# Create database if it doesn't exist
|
||||||
if ! PGPASSWORD=$DATABASE_PASSWORD psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USERNAME" -c "SELECT 1 FROM pg_database WHERE datname='$DATABASE_NAME'" | grep -q 1; then
|
if ! PGPASSWORD=$DATABASE_PASSWORD psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USERNAME" -d "$DATABASE_NAME" -c "SELECT 1 FROM pg_database WHERE datname='$DATABASE_NAME'" | grep -q 1; then
|
||||||
echo "Creating database $DATABASE_NAME..."
|
echo "Creating database $DATABASE_NAME..."
|
||||||
bundle exec rails db:create
|
PGPASSWORD=$DATABASE_PASSWORD createdb -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USERNAME" "$DATABASE_NAME"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run database migrations
|
# Run database migrations
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,12 @@ namespace :points do
|
||||||
task migrate_to_lonlat: :environment do
|
task migrate_to_lonlat: :environment do
|
||||||
puts 'Updating points to use lonlat...'
|
puts 'Updating points to use lonlat...'
|
||||||
|
|
||||||
|
points = Point.where(longitude: nil, latitude: nil).select(:id, :longitude, :latitude, :raw_data)
|
||||||
|
|
||||||
|
points.find_each do |point|
|
||||||
|
Points::RawDataLonlatExtractor.new(point).call
|
||||||
|
end
|
||||||
|
|
||||||
ActiveRecord::Base.connection.execute('REINDEX TABLE points;')
|
ActiveRecord::Base.connection.execute('REINDEX TABLE points;')
|
||||||
|
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe TelemetrySendingJob, type: :job do
|
|
||||||
describe '#perform' do
|
|
||||||
let(:gather_service) { instance_double(Telemetry::Gather) }
|
|
||||||
let(:send_service) { instance_double(Telemetry::Send) }
|
|
||||||
let(:telemetry_data) { { some: 'data' } }
|
|
||||||
|
|
||||||
before do
|
|
||||||
allow(Telemetry::Gather).to receive(:new).and_return(gather_service)
|
|
||||||
allow(gather_service).to receive(:call).and_return(telemetry_data)
|
|
||||||
allow(Telemetry::Send).to receive(:new).with(telemetry_data).and_return(send_service)
|
|
||||||
allow(send_service).to receive(:call)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with default env' do
|
|
||||||
it 'does not send telemetry data' do
|
|
||||||
described_class.perform_now
|
|
||||||
|
|
||||||
expect(send_service).not_to have_received(:call)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when ENABLE_TELEMETRY is set to true' do
|
|
||||||
before do
|
|
||||||
stub_const('ENV', ENV.to_h.merge('ENABLE_TELEMETRY' => 'true'))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'gathers telemetry data and sends it' do
|
|
||||||
described_class.perform_now
|
|
||||||
|
|
||||||
expect(gather_service).to have_received(:call)
|
|
||||||
expect(send_service).to have_received(:call)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -77,4 +77,65 @@ RSpec.describe 'Imports', type: :request do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'GET /imports/new' do
|
||||||
|
context 'when user is logged in' do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
|
||||||
|
before { sign_in user }
|
||||||
|
|
||||||
|
it 'returns http success' do
|
||||||
|
get new_import_path
|
||||||
|
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'DELETE /imports/:id' do
|
||||||
|
context 'when user is logged in' do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let!(:import) { create(:import, user:) }
|
||||||
|
|
||||||
|
before { sign_in user }
|
||||||
|
|
||||||
|
it 'deletes the import' do
|
||||||
|
expect do
|
||||||
|
delete import_path(import)
|
||||||
|
end.to change(user.imports, :count).by(-1)
|
||||||
|
|
||||||
|
expect(response).to redirect_to(imports_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'GET /imports/:id/edit' do
|
||||||
|
context 'when user is logged in' do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:import) { create(:import, user:) }
|
||||||
|
|
||||||
|
before { sign_in user }
|
||||||
|
|
||||||
|
it 'returns http success' do
|
||||||
|
get edit_import_path(import)
|
||||||
|
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'PATCH /imports/:id' do
|
||||||
|
context 'when user is logged in' do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:import) { create(:import, user:) }
|
||||||
|
|
||||||
|
before { sign_in user }
|
||||||
|
|
||||||
|
it 'updates the import' do
|
||||||
|
patch import_path(import), params: { import: { name: 'New Name' } }
|
||||||
|
|
||||||
|
expect(response).to redirect_to(imports_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,24 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
require 'sidekiq/web'
|
||||||
|
|
||||||
RSpec.describe '/sidekiq', type: :request do
|
RSpec.describe '/sidekiq', type: :request do
|
||||||
|
before do
|
||||||
|
# Allow any ENV key to be accessed and return nil by default
|
||||||
|
allow(ENV).to receive(:[]).and_return(nil)
|
||||||
|
|
||||||
|
# Stub Sidekiq::Web with a simple Rack app for testing
|
||||||
|
allow(Sidekiq::Web).to receive(:call) do |_env|
|
||||||
|
[200, { 'Content-Type' => 'text/html' }, ['Sidekiq Web UI']]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when Dawarich is in self-hosted mode' do
|
context 'when Dawarich is in self-hosted mode' do
|
||||||
before do
|
before do
|
||||||
allow(DawarichSettings).to receive(:self_hosted?).and_return(true)
|
allow(DawarichSettings).to receive(:self_hosted?).and_return(true)
|
||||||
|
allow(ENV).to receive(:[]).with('SIDEKIQ_USERNAME').and_return(nil)
|
||||||
|
allow(ENV).to receive(:[]).with('SIDEKIQ_PASSWORD').and_return(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when user is not authenticated' do
|
context 'when user is not authenticated' do
|
||||||
|
|
@ -48,6 +61,8 @@ RSpec.describe '/sidekiq', type: :request do
|
||||||
context 'when Dawarich is not in self-hosted mode' do
|
context 'when Dawarich is not in self-hosted mode' do
|
||||||
before do
|
before do
|
||||||
allow(DawarichSettings).to receive(:self_hosted?).and_return(false)
|
allow(DawarichSettings).to receive(:self_hosted?).and_return(false)
|
||||||
|
allow(ENV).to receive(:[]).with('SIDEKIQ_USERNAME').and_return(nil)
|
||||||
|
allow(ENV).to receive(:[]).with('SIDEKIQ_PASSWORD').and_return(nil)
|
||||||
Rails.application.reload_routes!
|
Rails.application.reload_routes!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -70,4 +85,41 @@ RSpec.describe '/sidekiq', type: :request do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when SIDEKIQ_USERNAME and SIDEKIQ_PASSWORD are set' do
|
||||||
|
before do
|
||||||
|
allow(DawarichSettings).to receive(:self_hosted?).and_return(false)
|
||||||
|
allow(ENV).to receive(:[]).with('SIDEKIQ_USERNAME').and_return('admin')
|
||||||
|
allow(ENV).to receive(:[]).with('SIDEKIQ_PASSWORD').and_return('password')
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is not authenticated' do
|
||||||
|
it 'redirects to sign in page' do
|
||||||
|
get sidekiq_url
|
||||||
|
|
||||||
|
expect(response).to redirect_to('/users/sign_in')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is not admin' do
|
||||||
|
before { sign_in create(:user) }
|
||||||
|
|
||||||
|
it 'redirects to root page' do
|
||||||
|
get sidekiq_url
|
||||||
|
|
||||||
|
expect(response).to redirect_to(root_url)
|
||||||
|
expect(flash[:error]).to eq('You are not authorized to perform this action.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is admin' do
|
||||||
|
before { sign_in create(:user, :admin) }
|
||||||
|
|
||||||
|
it 'renders a successful response' do
|
||||||
|
get sidekiq_url
|
||||||
|
|
||||||
|
expect(response).to be_successful
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,14 @@ RSpec.describe Photos::ImportParser do
|
||||||
let(:immich_data) do
|
let(:immich_data) do
|
||||||
JSON.parse(File.read(Rails.root.join('spec/fixtures/files/immich/geodata.json')))
|
JSON.parse(File.read(Rails.root.join('spec/fixtures/files/immich/geodata.json')))
|
||||||
end
|
end
|
||||||
let(:import) { create(:import, user:, raw_data: immich_data) }
|
let(:import) { create(:import, user:) }
|
||||||
|
|
||||||
|
let(:file_path) { Rails.root.join('spec/fixtures/files/immich/geodata.json') }
|
||||||
|
let(:file) { Rack::Test::UploadedFile.new(file_path, 'text/plain') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
import.file.attach(io: File.open(file_path), filename: 'immich_geodata.json', content_type: 'application/json')
|
||||||
|
end
|
||||||
|
|
||||||
context 'when there are no points' do
|
context 'when there are no points' do
|
||||||
it 'creates new points' do
|
it 'creates new points' do
|
||||||
|
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Telemetry::Gather do
|
|
||||||
let!(:user) { create(:user, last_sign_in_at: Time.zone.today) }
|
|
||||||
|
|
||||||
describe '#call' do
|
|
||||||
subject(:gather) { described_class.new.call }
|
|
||||||
|
|
||||||
it 'returns a hash with measurement, timestamp, tags, and fields' do
|
|
||||||
expect(gather).to include(:measurement, :timestamp, :tags, :fields)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'includes the correct measurement' do
|
|
||||||
expect(gather[:measurement]).to eq('dawarich_usage_metrics')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'includes the current timestamp' do
|
|
||||||
expect(gather[:timestamp]).to be_within(1).of(Time.current.to_i)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'includes the correct instance_id in tags' do
|
|
||||||
expect(gather[:tags][:instance_id]).to eq(Digest::SHA2.hexdigest(user.api_key))
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'includes the correct app_version in fields' do
|
|
||||||
expect(gather[:fields][:app_version]).to eq("\"#{APP_VERSION}\"")
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'includes the correct dau in fields' do
|
|
||||||
expect(gather[:fields][:dau]).to eq(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with a custom measurement' do
|
|
||||||
let(:measurement) { 'custom_measurement' }
|
|
||||||
|
|
||||||
subject(:gather) { described_class.new(measurement:).call }
|
|
||||||
|
|
||||||
it 'includes the correct measurement' do
|
|
||||||
expect(gather[:measurement]).to eq('custom_measurement')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Loading…
Reference in a new issue