diff --git a/Gemfile b/Gemfile
index eb432aed..2b3f7406 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,6 +6,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '3.2.3'
gem 'bootsnap', require: false
gem 'chartkick'
+gem 'data_migrate'
gem 'devise'
gem 'geocoder'
gem 'importmap-rails'
diff --git a/Gemfile.lock b/Gemfile.lock
index b2c9bfb7..fc2ea24e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -96,6 +96,9 @@ GEM
rexml
crass (1.0.6)
csv (3.3.0)
+ data_migrate (9.4.0)
+ activerecord (>= 6.1)
+ railties (>= 6.1)
date (3.3.4)
debug (1.9.2)
irb (~> 1.10)
@@ -391,6 +394,7 @@ PLATFORMS
DEPENDENCIES
bootsnap
chartkick
+ data_migrate
debug
devise
dotenv-rails
diff --git a/app/controllers/api/v1/overland/batches_controller.rb b/app/controllers/api/v1/overland/batches_controller.rb
index 014b43f5..3aa3cd39 100644
--- a/app/controllers/api/v1/overland/batches_controller.rb
+++ b/app/controllers/api/v1/overland/batches_controller.rb
@@ -5,7 +5,7 @@ class Api::V1::Overland::BatchesController < ApplicationController
before_action :authenticate_api_key
def create
- Overland::BatchCreatingJob.perform_later(batch_params)
+ Overland::BatchCreatingJob.perform_later(batch_params, current_api_user.id)
render json: { result: 'ok' }, status: :created
end
diff --git a/app/controllers/api/v1/owntracks/points_controller.rb b/app/controllers/api/v1/owntracks/points_controller.rb
index e610a07a..c28d6df7 100644
--- a/app/controllers/api/v1/owntracks/points_controller.rb
+++ b/app/controllers/api/v1/owntracks/points_controller.rb
@@ -5,7 +5,7 @@ class Api::V1::PointsController < ApplicationController
before_action :authenticate_api_key
def create
- Owntracks::PointCreatingJob.perform_later(point_params)
+ Owntracks::PointCreatingJob.perform_later(point_params, current_user.id)
render json: {}, status: :ok
end
diff --git a/app/controllers/export_controller.rb b/app/controllers/export_controller.rb
index cbd3e712..833d88ef 100644
--- a/app/controllers/export_controller.rb
+++ b/app/controllers/export_controller.rb
@@ -24,7 +24,7 @@ class ExportController < ApplicationController
end
def start_at
- first_point_timestamp = Point.order(timestamp: :asc)&.first&.timestamp
+ first_point_timestamp = current_user.tracked_points.order(timestamp: :asc)&.first&.timestamp
@start_at ||=
if params[:start_at].nil? && first_point_timestamp.present?
@@ -37,7 +37,7 @@ class ExportController < ApplicationController
end
def end_at
- last_point_timestamp = Point.order(timestamp: :desc)&.last&.timestamp
+ last_point_timestamp = current_user.tracked_points.order(timestamp: :desc)&.last&.timestamp
@end_at ||=
if params[:end_at].nil? && last_point_timestamp.present?
diff --git a/app/controllers/map_controller.rb b/app/controllers/map_controller.rb
index 01cee763..794b0dbd 100644
--- a/app/controllers/map_controller.rb
+++ b/app/controllers/map_controller.rb
@@ -3,7 +3,7 @@ class MapController < ApplicationController
before_action :authenticate_user!
def index
- @points = Point.without_raw_data.where('timestamp >= ? AND timestamp <= ?', start_at, end_at).order(timestamp: :asc)
+ @points = current_user.tracked_points.without_raw_data.where('timestamp >= ? AND timestamp <= ?', start_at, end_at).order(timestamp: :asc)
@countries_and_cities = CountriesAndCities.new(@points).call
@coordinates =
diff --git a/app/controllers/points_controller.rb b/app/controllers/points_controller.rb
index 2c08c11d..179daf7a 100644
--- a/app/controllers/points_controller.rb
+++ b/app/controllers/points_controller.rb
@@ -5,7 +5,8 @@ class PointsController < ApplicationController
def index
@points =
- Point
+ current_user
+ .tracked_points
.without_raw_data
.where('timestamp >= ? AND timestamp <= ?', start_at, end_at)
.order(timestamp: :asc)
@@ -16,9 +17,9 @@ class PointsController < ApplicationController
end
def bulk_destroy
- Point.where(id: params[:point_ids].compact).destroy_all
+ current_user.tracked_points.where(id: params[:point_ids].compact).destroy_all
- redirect_to points_url, notice: "Points were successfully destroyed.", status: :see_other
+ redirect_to points_url, notice: 'Points were successfully destroyed.', status: :see_other
end
private
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 0b1f8c90..52256b5f 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -38,7 +38,7 @@ module ApplicationHelper
end
def countries_and_cities_stat(year)
- data = Stat.year_cities_and_countries(year)
+ data = Stat.year_cities_and_countries(year, user)
countries = data[:countries]
cities = data[:cities]
diff --git a/app/jobs/overland/batch_creating_job.rb b/app/jobs/overland/batch_creating_job.rb
index 6172e4d5..a3d6da1d 100644
--- a/app/jobs/overland/batch_creating_job.rb
+++ b/app/jobs/overland/batch_creating_job.rb
@@ -3,11 +3,11 @@
class Overland::BatchCreatingJob < ApplicationJob
queue_as :default
- def perform(params)
+ def perform(params, user_id)
data = Overland::Params.new(params).call
data.each do |location|
- Point.create!(location)
+ Point.create!(location.merge(user_id:))
end
end
end
diff --git a/app/jobs/owntracks/point_creating_job.rb b/app/jobs/owntracks/point_creating_job.rb
index 928d75a0..d4df3971 100644
--- a/app/jobs/owntracks/point_creating_job.rb
+++ b/app/jobs/owntracks/point_creating_job.rb
@@ -3,9 +3,10 @@
class Owntracks::PointCreatingJob < ApplicationJob
queue_as :default
- def perform(point_params)
+ # TODO: after deprecation of old endpoint, make user_id required
+ def perform(point_params, user_id = nil)
parsed_params = OwnTracks::Params.new(point_params).call
- Point.create(parsed_params)
+ Point.create!(parsed_params.merge(user_id:))
end
end
diff --git a/app/models/point.rb b/app/models/point.rb
index c15984e5..df627fc9 100644
--- a/app/models/point.rb
+++ b/app/models/point.rb
@@ -4,6 +4,7 @@ class Point < ApplicationRecord
# self.ignored_columns = %w[raw_data]
belongs_to :import, optional: true
+ belongs_to :user, optional: true
validates :latitude, :longitude, :timestamp, presence: true
diff --git a/app/models/stat.rb b/app/models/stat.rb
index 3caa33c5..da10fece 100644
--- a/app/models/stat.rb
+++ b/app/models/stat.rb
@@ -11,7 +11,7 @@ class Stat < ApplicationRecord
end_of_day = day.end_of_day.to_i
# We have to filter by user as well
- points = Point.without_raw_data.where(timestamp: beginning_of_day..end_of_day)
+ points = user.tracked_points.without_raw_data.where(timestamp: beginning_of_day..end_of_day)
data = { day: index, distance: 0 }
@@ -40,8 +40,8 @@ class Stat < ApplicationRecord
end
end
- def self.year_cities_and_countries(year)
- points = Point.where(timestamp: DateTime.new(year).beginning_of_year..DateTime.new(year).end_of_year)
+ def self.year_cities_and_countries(year, user)
+ points = user.tracked_points.where(timestamp: DateTime.new(year).beginning_of_year..DateTime.new(year).end_of_year)
data = CountriesAndCities.new(points).call
diff --git a/app/models/user.rb b/app/models/user.rb
index de70dd3e..2201ef14 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -9,6 +9,7 @@ class User < ApplicationRecord
has_many :imports, dependent: :destroy
has_many :points, through: :imports
has_many :stats, dependent: :destroy
+ has_many :tracked_points, class_name: 'Point', dependent: :destroy
after_create :create_api_key
diff --git a/app/views/stats/index.html.erb b/app/views/stats/index.html.erb
index 92951adb..33508af3 100644
--- a/app/views/stats/index.html.erb
+++ b/app/views/stats/index.html.erb
@@ -83,7 +83,7 @@
<% if REVERSE_GEOCODING_ENABLED %>
<% cache [current_user, 'countries_and_cities_stat', year], skip_digest: true do %>
- <%= countries_and_cities_stat(year) %>
+ <%= countries_and_cities_stat(year, current_user) %>
<% end %>
<% end %>
diff --git a/db/data/20240525110530_bind_existing_points_to_first_user.rb b/db/data/20240525110530_bind_existing_points_to_first_user.rb
new file mode 100644
index 00000000..21fab9a4
--- /dev/null
+++ b/db/data/20240525110530_bind_existing_points_to_first_user.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class BindExistingPointsToFirstUser < ActiveRecord::Migration[7.1]
+ def up
+ user = User.first
+
+ points = Point.where(user_id: nil)
+
+ points.update_all(user_id: user.id)
+
+ Rails.logger.info "Bound #{points.count} points to user #{user.email}"
+ end
+
+ def down
+ raise ActiveRecord::IrreversibleMigration
+ end
+end
diff --git a/db/data_schema.rb b/db/data_schema.rb
new file mode 100644
index 00000000..394d4c85
--- /dev/null
+++ b/db/data_schema.rb
@@ -0,0 +1 @@
+DataMigrate::Data.define(version: 20240525110530)
diff --git a/db/migrate/20240525110244_add_user_id_to_points.rb b/db/migrate/20240525110244_add_user_id_to_points.rb
new file mode 100644
index 00000000..9715621d
--- /dev/null
+++ b/db/migrate/20240525110244_add_user_id_to_points.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddUserIdToPoints < ActiveRecord::Migration[7.1]
+ def change
+ add_reference :points, :user, foreign_key: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 867f1b87..4130dfc1 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.1].define(version: 2024_05_18_095848) do
+ActiveRecord::Schema[7.1].define(version: 2024_05_25_110244) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -82,6 +82,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_05_18_095848) do
t.string "country"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.bigint "user_id"
t.index ["altitude"], name: "index_points_on_altitude"
t.index ["battery"], name: "index_points_on_battery"
t.index ["battery_status"], name: "index_points_on_battery_status"
@@ -92,6 +93,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_05_18_095848) do
t.index ["latitude", "longitude"], name: "index_points_on_latitude_and_longitude"
t.index ["timestamp"], name: "index_points_on_timestamp"
t.index ["trigger"], name: "index_points_on_trigger"
+ t.index ["user_id"], name: "index_points_on_user_id"
end
create_table "stats", force: :cascade do |t|
@@ -125,5 +127,6 @@ ActiveRecord::Schema[7.1].define(version: 2024_05_18_095848) do
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
+ add_foreign_key "points", "users"
add_foreign_key "stats", "users"
end
diff --git a/dev-docker-entrypoint.sh b/dev-docker-entrypoint.sh
index f50f6906..a21b8738 100644
--- a/dev-docker-entrypoint.sh
+++ b/dev-docker-entrypoint.sh
@@ -28,5 +28,9 @@ bundle exec rails db:create
echo "PostgreSQL is ready. Running database migrations..."
bundle exec rails db:prepare
+# Run data migrations
+echo "Running DATA migrations..."
+bundle exec rake data:migrate
+
# run passed commands
bundle exec ${@}
diff --git a/spec/jobs/overland/batch_creating_job_spec.rb b/spec/jobs/overland/batch_creating_job_spec.rb
index 63f5e2c8..d1d842c8 100644
--- a/spec/jobs/overland/batch_creating_job_spec.rb
+++ b/spec/jobs/overland/batch_creating_job_spec.rb
@@ -2,14 +2,21 @@ require 'rails_helper'
RSpec.describe Overland::BatchCreatingJob, type: :job do
describe '#perform' do
- subject(:perform) { described_class.new.perform(json) }
+ subject(:perform) { described_class.new.perform(json, user.id) }
let(:file_path) { 'spec/fixtures/files/overland/geodata.json' }
let(:file) { File.open(file_path) }
let(:json) { JSON.parse(file.read) }
+ let(:user) { create(:user) }
it 'creates a location' do
expect { perform }.to change { Point.count }.by(1)
end
+
+ it 'creates a point with the correct user_id' do
+ perform
+
+ expect(Point.last.user_id).to eq(user.id)
+ end
end
end
diff --git a/spec/jobs/owntracks/point_creating_job_spec.rb b/spec/jobs/owntracks/point_creating_job_spec.rb
index 625fbce6..7c15b5d3 100644
--- a/spec/jobs/owntracks/point_creating_job_spec.rb
+++ b/spec/jobs/owntracks/point_creating_job_spec.rb
@@ -2,14 +2,21 @@ require 'rails_helper'
RSpec.describe Owntracks::PointCreatingJob, type: :job do
describe '#perform' do
- subject(:perform) { described_class.new.perform(point_params) }
+ subject(:perform) { described_class.new.perform(point_params, user.id) }
let(:point_params) do
{ lat: 1.0, lon: 1.0, tid: 'test', tst: Time.now.to_i, topic: 'iPhone 12 pro' }
end
+ let(:user) { create(:user) }
it 'creates a point' do
expect { perform }.to change { Point.count }.by(1)
end
+
+ it 'creates a point with the correct user_id' do
+ perform
+
+ expect(Point.last.user_id).to eq(user.id)
+ end
end
end
diff --git a/spec/models/point_spec.rb b/spec/models/point_spec.rb
index 5ff7afdb..4548a9f6 100644
--- a/spec/models/point_spec.rb
+++ b/spec/models/point_spec.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe Point, type: :model do
describe 'associations' do
it { is_expected.to belong_to(:import).optional }
+ it { is_expected.to belong_to(:user).optional }
end
describe 'validations' do
diff --git a/spec/models/stat_spec.rb b/spec/models/stat_spec.rb
index 535d64cc..72999b5b 100644
--- a/spec/models/stat_spec.rb
+++ b/spec/models/stat_spec.rb
@@ -11,9 +11,10 @@ RSpec.describe Stat, type: :model do
describe 'methods' do
let(:year) { 2021 }
+ let(:user) { create(:user) }
describe '.year_cities_and_countries' do
- subject { described_class.year_cities_and_countries(year) }
+ subject { described_class.year_cities_and_countries(year, user) }
let(:timestamp) { DateTime.new(year, 1, 1, 0, 0, 0) }
@@ -24,16 +25,16 @@ RSpec.describe Stat, type: :model do
context 'when there are points' do
let!(:points) do
[
- create(:point, city: 'Berlin', country: 'Germany', timestamp:),
- create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 10.minutes),
- create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 20.minutes),
- create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 30.minutes),
- create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 40.minutes),
- create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 50.minutes),
- create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 60.minutes),
- create(:point, city: 'Berlin', country: 'Germany', timestamp: timestamp + 70.minutes),
- create(:point, city: 'Brugges', country: 'Belgium', timestamp: timestamp + 80.minutes),
- create(:point, city: 'Brugges', country: 'Belgium', timestamp: timestamp + 90.minutes)
+ create(:point, user:, city: 'Berlin', country: 'Germany', timestamp:),
+ create(:point, user:, city: 'Berlin', country: 'Germany', timestamp: timestamp + 10.minutes),
+ create(:point, user:, city: 'Berlin', country: 'Germany', timestamp: timestamp + 20.minutes),
+ create(:point, user:, city: 'Berlin', country: 'Germany', timestamp: timestamp + 30.minutes),
+ create(:point, user:, city: 'Berlin', country: 'Germany', timestamp: timestamp + 40.minutes),
+ create(:point, user:, city: 'Berlin', country: 'Germany', timestamp: timestamp + 50.minutes),
+ create(:point, user:, city: 'Berlin', country: 'Germany', timestamp: timestamp + 60.minutes),
+ create(:point, user:, city: 'Berlin', country: 'Germany', timestamp: timestamp + 70.minutes),
+ create(:point, user:, city: 'Brugges', country: 'Belgium', timestamp: timestamp + 80.minutes),
+ create(:point, user:, city: 'Brugges', country: 'Belgium', timestamp: timestamp + 90.minutes)
]
end
@@ -86,8 +87,8 @@ RSpec.describe Stat, type: :model do
context 'when there are points' do
let!(:points) do
- create(:point, latitude: 1, longitude: 1, timestamp: DateTime.new(year, 1, 1, 1))
- create(:point, latitude: 2, longitude: 2, timestamp: DateTime.new(year, 1, 1, 2))
+ create(:point, user:, latitude: 1, longitude: 1, timestamp: DateTime.new(year, 1, 1, 1))
+ create(:point, user:, latitude: 2, longitude: 2, timestamp: DateTime.new(year, 1, 1, 2))
end
before { expected_distance[0][1] = 157.23 }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 3407d6e0..4098ad6c 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe User, type: :model do
it { is_expected.to have_many(:imports).dependent(:destroy) }
it { is_expected.to have_many(:points).through(:imports) }
it { is_expected.to have_many(:stats) }
+ it { is_expected.to have_many(:tracked_points).class_name('Point').dependent(:destroy) }
end
describe 'callbacks' do
diff --git a/spec/requests/points_spec.rb b/spec/requests/points_spec.rb
index 1e583c56..b65f66c5 100644
--- a/spec/requests/points_spec.rb
+++ b/spec/requests/points_spec.rb
@@ -30,11 +30,12 @@ RSpec.describe '/points', type: :request do
end
describe 'DELETE /bulk_destroy' do
- let(:point1) { create(:point) }
- let(:point2) { create(:point) }
+ let(:user) { create(:user) }
+ let(:point1) { create(:point, user:) }
+ let(:point2) { create(:point, user:) }
before do
- sign_in create(:user)
+ sign_in user
end
it 'destroys the selected points' do
diff --git a/swagger/v1/swagger.yaml b/swagger/v1/swagger.yaml
index 530ad1c2..718e55fe 100644
--- a/swagger/v1/swagger.yaml
+++ b/swagger/v1/swagger.yaml
@@ -180,7 +180,7 @@ paths:
lat: 52.502397
lon: 13.356718
tid: Swagger
- tst: 1716634644
+ tst: 1716636414
servers:
- url: http://{defaultHost}
variables: