mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
Add device model with relations
This commit is contained in:
parent
dfbe9a9821
commit
a96517caf1
14 changed files with 82 additions and 4 deletions
|
|
@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
- Possibly fixed a bug where visits were no suggested correctly. #984
|
- Possibly fixed a bug where visits were no suggested correctly. #984
|
||||||
- Scratch map is now working correctly.
|
- Scratch map is now working correctly.
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- Internal data structure for separate devices in a single user account.
|
||||||
|
- [ ] Immich and Photoprism integrations should fill all possible fields in points table
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [0.30.7] - 2025-08-01
|
# [0.30.7] - 2025-08-01
|
||||||
|
|
|
||||||
6
app/models/device.rb
Normal file
6
app/models/device.rb
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
class Device < ApplicationRecord
|
||||||
|
belongs_to :user
|
||||||
|
|
||||||
|
validates :name, presence: true
|
||||||
|
validates :identifier, presence: true, uniqueness: { scope: :user_id }
|
||||||
|
end
|
||||||
|
|
@ -9,10 +9,11 @@ class Point < ApplicationRecord
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :country, optional: true
|
belongs_to :country, optional: true
|
||||||
belongs_to :track, optional: true
|
belongs_to :track, optional: true
|
||||||
|
belongs_to :device, optional: true
|
||||||
|
|
||||||
validates :timestamp, :lonlat, presence: true
|
validates :timestamp, :lonlat, presence: true
|
||||||
validates :lonlat, uniqueness: {
|
validates :lonlat, uniqueness: {
|
||||||
scope: %i[timestamp user_id],
|
scope: %i[timestamp user_id device_id],
|
||||||
message: 'already has a point at this location and time for this user',
|
message: 'already has a point at this location and time for this user',
|
||||||
index: true
|
index: true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ class User < ApplicationRecord
|
||||||
has_many :places, through: :visits
|
has_many :places, through: :visits
|
||||||
has_many :trips, dependent: :destroy
|
has_many :trips, dependent: :destroy
|
||||||
has_many :tracks, dependent: :destroy
|
has_many :tracks, dependent: :destroy
|
||||||
|
has_many :devices, dependent: :destroy
|
||||||
|
|
||||||
after_create :create_api_key
|
after_create :create_api_key
|
||||||
after_commit :activate, on: :create, if: -> { DawarichSettings.self_hosted? }
|
after_commit :activate, on: :create, if: -> { DawarichSettings.self_hosted? }
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
class PointSerializer
|
class PointSerializer
|
||||||
EXCLUDED_ATTRIBUTES = %w[
|
EXCLUDED_ATTRIBUTES = %w[
|
||||||
created_at updated_at visit_id id import_id user_id raw_data lonlat
|
created_at updated_at visit_id id import_id user_id raw_data lonlat
|
||||||
reverse_geocoded_at country_id
|
reverse_geocoded_at country_id device_id
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
def initialize(point)
|
def initialize(point)
|
||||||
|
|
|
||||||
14
db/migrate/20250805184854_create_devices.rb
Normal file
14
db/migrate/20250805184854_create_devices.rb
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreateDevices < ActiveRecord::Migration[8.0]
|
||||||
|
def change
|
||||||
|
create_table :devices do |t|
|
||||||
|
t.string :name, null: false
|
||||||
|
t.references :user, null: false, foreign_key: true
|
||||||
|
t.string :identifier, null: false
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
add_index :devices, :identifier
|
||||||
|
end
|
||||||
|
end
|
||||||
9
db/migrate/20250805184855_add_device_id_to_points.rb
Normal file
9
db/migrate/20250805184855_add_device_id_to_points.rb
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddDeviceIdToPoints < ActiveRecord::Migration[8.0]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_reference :points, :device, null: true, index: { algorithm: :concurrently }
|
||||||
|
end
|
||||||
|
end
|
||||||
15
db/schema.rb
generated
15
db/schema.rb
generated
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2025_07_28_191359) do
|
ActiveRecord::Schema[8.0].define(version: 2025_08_05_184855) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pg_catalog.plpgsql"
|
enable_extension "pg_catalog.plpgsql"
|
||||||
enable_extension "postgis"
|
enable_extension "postgis"
|
||||||
|
|
@ -80,6 +80,16 @@ ActiveRecord::Schema[8.0].define(version: 2025_07_28_191359) do
|
||||||
create_table "data_migrations", primary_key: "version", id: :string, force: :cascade do |t|
|
create_table "data_migrations", primary_key: "version", id: :string, force: :cascade do |t|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "devices", force: :cascade do |t|
|
||||||
|
t.string "name", null: false
|
||||||
|
t.bigint "user_id", null: false
|
||||||
|
t.string "identifier", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["identifier"], name: "index_devices_on_identifier"
|
||||||
|
t.index ["user_id"], name: "index_devices_on_user_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "exports", force: :cascade do |t|
|
create_table "exports", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "url"
|
t.string "url"
|
||||||
|
|
@ -187,6 +197,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_07_28_191359) do
|
||||||
t.bigint "country_id"
|
t.bigint "country_id"
|
||||||
t.bigint "track_id"
|
t.bigint "track_id"
|
||||||
t.string "country_name"
|
t.string "country_name"
|
||||||
|
t.bigint "device_id"
|
||||||
t.index ["altitude"], name: "index_points_on_altitude"
|
t.index ["altitude"], name: "index_points_on_altitude"
|
||||||
t.index ["battery"], name: "index_points_on_battery"
|
t.index ["battery"], name: "index_points_on_battery"
|
||||||
t.index ["battery_status"], name: "index_points_on_battery_status"
|
t.index ["battery_status"], name: "index_points_on_battery_status"
|
||||||
|
|
@ -195,6 +206,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_07_28_191359) do
|
||||||
t.index ["country"], name: "index_points_on_country"
|
t.index ["country"], name: "index_points_on_country"
|
||||||
t.index ["country_id"], name: "index_points_on_country_id"
|
t.index ["country_id"], name: "index_points_on_country_id"
|
||||||
t.index ["country_name"], name: "index_points_on_country_name"
|
t.index ["country_name"], name: "index_points_on_country_name"
|
||||||
|
t.index ["device_id"], name: "index_points_on_device_id"
|
||||||
t.index ["external_track_id"], name: "index_points_on_external_track_id"
|
t.index ["external_track_id"], name: "index_points_on_external_track_id"
|
||||||
t.index ["geodata"], name: "index_points_on_geodata", using: :gin
|
t.index ["geodata"], name: "index_points_on_geodata", using: :gin
|
||||||
t.index ["import_id"], name: "index_points_on_import_id"
|
t.index ["import_id"], name: "index_points_on_import_id"
|
||||||
|
|
@ -300,6 +312,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_07_28_191359) do
|
||||||
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
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 "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
||||||
add_foreign_key "areas", "users"
|
add_foreign_key "areas", "users"
|
||||||
|
add_foreign_key "devices", "users"
|
||||||
add_foreign_key "notifications", "users"
|
add_foreign_key "notifications", "users"
|
||||||
add_foreign_key "place_visits", "places"
|
add_foreign_key "place_visits", "places"
|
||||||
add_foreign_key "place_visits", "visits"
|
add_foreign_key "place_visits", "visits"
|
||||||
|
|
|
||||||
9
spec/factories/devices.rb
Normal file
9
spec/factories/devices.rb
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
FactoryBot.define do
|
||||||
|
factory :device do
|
||||||
|
name { SecureRandom.uuid }
|
||||||
|
user
|
||||||
|
identifier { SecureRandom.uuid }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -31,6 +31,7 @@ FactoryBot.define do
|
||||||
lonlat { "POINT(#{FFaker::Geolocation.lng} #{FFaker::Geolocation.lat})" }
|
lonlat { "POINT(#{FFaker::Geolocation.lng} #{FFaker::Geolocation.lat})" }
|
||||||
user
|
user
|
||||||
country_id { nil }
|
country_id { nil }
|
||||||
|
device
|
||||||
|
|
||||||
# Add transient attribute to handle country strings
|
# Add transient attribute to handle country strings
|
||||||
transient do
|
transient do
|
||||||
|
|
|
||||||
13
spec/models/device_spec.rb
Normal file
13
spec/models/device_spec.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Device, type: :model do
|
||||||
|
describe 'validations' do
|
||||||
|
subject { build(:device) }
|
||||||
|
|
||||||
|
it { is_expected.to validate_presence_of(:name) }
|
||||||
|
it { is_expected.to validate_presence_of(:identifier) }
|
||||||
|
it { is_expected.to validate_uniqueness_of(:identifier).scoped_to(:user_id) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -9,11 +9,15 @@ RSpec.describe Point, type: :model do
|
||||||
it { is_expected.to belong_to(:country).optional }
|
it { is_expected.to belong_to(:country).optional }
|
||||||
it { is_expected.to belong_to(:visit).optional }
|
it { is_expected.to belong_to(:visit).optional }
|
||||||
it { is_expected.to belong_to(:track).optional }
|
it { is_expected.to belong_to(:track).optional }
|
||||||
|
it { is_expected.to belong_to(:device).optional }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'validations' do
|
describe 'validations' do
|
||||||
|
subject { build(:point, timestamp: Time.current, lonlat: 'POINT(1.0 2.0)') }
|
||||||
|
|
||||||
it { is_expected.to validate_presence_of(:timestamp) }
|
it { is_expected.to validate_presence_of(:timestamp) }
|
||||||
it { is_expected.to validate_presence_of(:lonlat) }
|
it { is_expected.to validate_presence_of(:lonlat) }
|
||||||
|
it { is_expected.to validate_uniqueness_of(:lonlat).scoped_to(%i[timestamp user_id device_id]).with_message('already has a point at this location and time for this user') }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'callbacks' do
|
describe 'callbacks' do
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ RSpec.describe User, type: :model do
|
||||||
it { is_expected.to have_many(:places).through(:visits) }
|
it { is_expected.to have_many(:places).through(:visits) }
|
||||||
it { is_expected.to have_many(:trips).dependent(:destroy) }
|
it { is_expected.to have_many(:trips).dependent(:destroy) }
|
||||||
it { is_expected.to have_many(:tracks).dependent(:destroy) }
|
it { is_expected.to have_many(:tracks).dependent(:destroy) }
|
||||||
|
it { is_expected.to have_many(:devices).dependent(:destroy) }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'enums' do
|
describe 'enums' do
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ RSpec.describe Photos::Importer do
|
||||||
let(:user) do
|
let(:user) do
|
||||||
create(:user, settings: { 'immich_url' => 'http://immich.app', 'immich_api_key' => '123456' })
|
create(:user, settings: { 'immich_url' => 'http://immich.app', 'immich_api_key' => '123456' })
|
||||||
end
|
end
|
||||||
|
let(:device) { create(:device, user:) }
|
||||||
|
|
||||||
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')))
|
||||||
|
|
@ -44,7 +45,7 @@ RSpec.describe Photos::Importer do
|
||||||
|
|
||||||
context 'when there are points with the same coordinates' do
|
context 'when there are points with the same coordinates' do
|
||||||
let!(:existing_point) do
|
let!(:existing_point) do
|
||||||
create(:point, lonlat: 'POINT(30.0000 59.0000)', timestamp: 978_296_400, user:)
|
create(:point, lonlat: 'POINT(30.0000 59.0000)', timestamp: 978_296_400, user:, device:)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates only new points' do
|
it 'creates only new points' do
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue